3 namespace Drupal\Tests\datetime_range\Functional;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Datetime\DrupalDateTime;
8 use Drupal\Core\Datetime\Entity\DateFormat;
9 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
10 use Drupal\Tests\datetime\Functional\DateTestBase;
11 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
12 use Drupal\entity_test\Entity\EntityTest;
13 use Drupal\field\Entity\FieldConfig;
14 use Drupal\field\Entity\FieldStorageConfig;
15 use Drupal\node\Entity\Node;
18 * Tests Daterange field functionality.
22 class DateRangeFieldTest extends DateTestBase {
29 public static $modules = ['datetime_range'];
32 * The default display settings to use for the formatters.
36 protected $defaultSettings = ['timezone_override' => '', 'separator' => '-'];
41 protected function getTestFieldType() {
46 * Tests date field functionality.
48 public function testDateRangeField() {
49 $field_name = $this->fieldStorage->getName();
50 $field_label = $this->field->label();
52 // Loop through defined timezones to test that date-only fields work at the
54 foreach (static::$timezones as $timezone) {
56 $this->setSiteTimezone($timezone);
57 $this->assertEquals($timezone, $this->config('system.date')->get('timezone.default'), 'Time zone set to ' . $timezone);
59 // Ensure field is set to a date-only field.
60 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
61 $this->fieldStorage->save();
63 // Display creation form.
64 $this->drupalGet('entity_test/add');
65 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
66 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
67 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
68 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
69 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
70 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
71 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
72 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
74 // Build up dates in the UTC timezone.
75 $value = '2012-12-31 00:00:00';
76 $start_date = new DrupalDateTime($value, 'UTC');
77 $end_value = '2013-06-06 00:00:00';
78 $end_date = new DrupalDateTime($end_value, 'UTC');
80 // Submit a valid date and ensure it is accepted.
81 $date_format = DateFormat::load('html_date')->getPattern();
82 $time_format = DateFormat::load('html_time')->getPattern();
85 "{$field_name}[0][value][date]" => $start_date->format($date_format),
86 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
88 $this->drupalPostForm(NULL, $edit, t('Save'));
89 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
91 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
92 $this->assertRaw($start_date->format($date_format));
93 $this->assertNoRaw($start_date->format($time_format));
94 $this->assertRaw($end_date->format($date_format));
95 $this->assertNoRaw($end_date->format($time_format));
97 // Verify the date doesn't change when entity is edited through the form.
98 $entity = EntityTest::load($id);
99 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
100 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
101 $this->drupalGet('entity_test/manage/' . $id . '/edit');
102 $this->drupalPostForm(NULL, [], t('Save'));
103 $this->drupalGet('entity_test/manage/' . $id . '/edit');
104 $this->drupalPostForm(NULL, [], t('Save'));
105 $this->drupalGet('entity_test/manage/' . $id . '/edit');
106 $this->drupalPostForm(NULL, [], t('Save'));
107 $entity = EntityTest::load($id);
108 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
109 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
111 // Formats that display a time component for date-only fields will display
112 // the default time, so that is applied before calculating the expected
114 $this->massageTestDate($start_date);
115 $this->massageTestDate($end_date);
117 // Reset display options since these get changed below.
118 $this->displayOptions = [
119 'type' => 'daterange_default',
122 'format_type' => 'long',
123 'separator' => 'THESEPARATOR',
124 ] + $this->defaultSettings,
127 // Verify that the default formatter works.
128 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
129 ->setComponent($field_name, $this->displayOptions)
132 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
133 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
134 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
135 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
136 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
137 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
138 $output = $this->renderTestEntity($id);
139 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
141 '%expected' => $start_expected,
142 '%expected_iso' => $start_expected_iso,
143 '%timezone' => $timezone,
145 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
147 '%expected' => $end_expected,
148 '%expected_iso' => $end_expected_iso,
149 '%timezone' => $timezone,
151 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
153 // Verify that hook_entity_prepare_view can add attributes.
154 // @see entity_test_entity_prepare_view()
155 $this->drupalGet('entity_test/' . $id);
156 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
158 // Verify that the plain formatter works.
159 $this->displayOptions['type'] = 'daterange_plain';
160 $this->displayOptions['settings'] = $this->defaultSettings;
161 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
162 ->setComponent($field_name, $this->displayOptions)
164 $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
165 $output = $this->renderTestEntity($id);
166 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
167 '%expected' => $expected,
168 '%timezone' => $timezone,
171 // Verify that the custom formatter works.
172 $this->displayOptions['type'] = 'daterange_custom';
173 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
174 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
175 ->setComponent($field_name, $this->displayOptions)
177 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
178 $output = $this->renderTestEntity($id);
179 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
180 '%expected' => $expected,
181 '%timezone' => $timezone,
184 // Test that allowed markup in custom format is preserved and XSS is
186 $this->displayOptions['settings']['date_format'] = '\\<\\s\\t\\r\\o\\n\\g\\>m/d/Y\\<\\/\\s\\t\\r\\o\\n\\g\\>\\<\\s\\c\\r\\i\\p\\t\\>\\a\\l\\e\\r\\t\\(\\S\\t\\r\\i\\n\\g\\.\\f\\r\\o\\m\\C\\h\\a\\r\\C\\o\\d\\e\\(\\8\\8\\,\\8\\3\\,\\8\\3\\)\\)\\<\\/\\s\\c\\r\\i\\p\\t\\>';
187 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
188 ->setComponent($field_name, $this->displayOptions)
190 $expected = '<strong>' . $start_date->format('m/d/Y') . '</strong>alert(String.fromCharCode(88,83,83)) - <strong>' . $end_date->format('m/d/Y') . '</strong>alert(String.fromCharCode(88,83,83))';
191 $output = $this->renderTestEntity($id);
192 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
193 '%expected' => $expected,
194 '%timezone' => $timezone,
197 // Test formatters when start date and end date are the same
198 $this->drupalGet('entity_test/add');
199 $value = '2012-12-31 00:00:00';
200 $start_date = new DrupalDateTime($value, 'UTC');
202 $date_format = DateFormat::load('html_date')->getPattern();
203 $time_format = DateFormat::load('html_time')->getPattern();
206 "{$field_name}[0][value][date]" => $start_date->format($date_format),
207 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
210 $this->drupalPostForm(NULL, $edit, t('Save'));
211 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
213 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
215 $this->massageTestDate($start_date);
217 $this->displayOptions = [
218 'type' => 'daterange_default',
221 'format_type' => 'long',
222 'separator' => 'THESEPARATOR',
223 ] + $this->defaultSettings,
226 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
227 ->setComponent($field_name, $this->displayOptions)
230 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
231 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
232 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
233 $output = $this->renderTestEntity($id);
234 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
236 '%expected' => $start_expected,
237 '%expected_iso' => $start_expected_iso,
238 '%timezone' => $timezone,
240 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page in ' . $timezone);
242 // Verify that hook_entity_prepare_view can add attributes.
243 // @see entity_test_entity_prepare_view()
244 $this->drupalGet('entity_test/' . $id);
245 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
247 $this->displayOptions['type'] = 'daterange_plain';
248 $this->displayOptions['settings'] = $this->defaultSettings;
249 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
250 ->setComponent($field_name, $this->displayOptions)
252 $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
253 $output = $this->renderTestEntity($id);
254 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
255 '%expected' => $expected,
256 '%timezone' => $timezone,
258 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
260 $this->displayOptions['type'] = 'daterange_custom';
261 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
262 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
263 ->setComponent($field_name, $this->displayOptions)
265 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
266 $output = $this->renderTestEntity($id);
267 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
268 '%expected' => $expected,
269 '%timezone' => $timezone,
271 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
276 * Tests date and time field.
278 public function testDatetimeRangeField() {
279 $field_name = $this->fieldStorage->getName();
280 $field_label = $this->field->label();
282 // Ensure the field to a datetime field.
283 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
284 $this->fieldStorage->save();
286 // Display creation form.
287 $this->drupalGet('entity_test/add');
288 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
289 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
290 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
291 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
292 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
293 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
294 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
296 // Build up dates in the UTC timezone.
297 $value = '2012-12-31 00:00:00';
298 $start_date = new DrupalDateTime($value, 'UTC');
299 $end_value = '2013-06-06 00:00:00';
300 $end_date = new DrupalDateTime($end_value, 'UTC');
302 // Update the timezone to the system default.
303 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
304 $end_date->setTimezone(timezone_open(drupal_get_user_timezone()));
306 // Submit a valid date and ensure it is accepted.
307 $date_format = DateFormat::load('html_date')->getPattern();
308 $time_format = DateFormat::load('html_time')->getPattern();
311 "{$field_name}[0][value][date]" => $start_date->format($date_format),
312 "{$field_name}[0][value][time]" => $start_date->format($time_format),
313 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
314 "{$field_name}[0][end_value][time]" => $end_date->format($time_format),
316 $this->drupalPostForm(NULL, $edit, t('Save'));
317 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
319 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
320 $this->assertRaw($start_date->format($date_format));
321 $this->assertRaw($start_date->format($time_format));
322 $this->assertRaw($end_date->format($date_format));
323 $this->assertRaw($end_date->format($time_format));
325 // Verify that the default formatter works.
326 $this->displayOptions['settings'] = [
327 'format_type' => 'long',
328 'separator' => 'THESEPARATOR',
329 ] + $this->defaultSettings;
330 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
331 ->setComponent($field_name, $this->displayOptions)
334 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
335 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
336 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
337 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
338 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
339 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
340 $output = $this->renderTestEntity($id);
341 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
342 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
343 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
345 // Verify that hook_entity_prepare_view can add attributes.
346 // @see entity_test_entity_prepare_view()
347 $this->drupalGet('entity_test/' . $id);
348 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
350 // Verify that the plain formatter works.
351 $this->displayOptions['type'] = 'daterange_plain';
352 $this->displayOptions['settings'] = $this->defaultSettings;
353 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
354 ->setComponent($field_name, $this->displayOptions)
356 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
357 $output = $this->renderTestEntity($id);
358 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
360 // Verify that the 'datetime_custom' formatter works.
361 $this->displayOptions['type'] = 'daterange_custom';
362 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
363 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
364 ->setComponent($field_name, $this->displayOptions)
366 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
367 $output = $this->renderTestEntity($id);
368 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
370 // Verify that the 'timezone_override' setting works.
371 $this->displayOptions['type'] = 'daterange_custom';
372 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
373 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
374 ->setComponent($field_name, $this->displayOptions)
376 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
377 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
378 $output = $this->renderTestEntity($id);
379 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
381 // Test formatters when start date and end date are the same
382 $this->drupalGet('entity_test/add');
383 $value = '2012-12-31 00:00:00';
384 $start_date = new DrupalDateTime($value, 'UTC');
385 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
387 $date_format = DateFormat::load('html_date')->getPattern();
388 $time_format = DateFormat::load('html_time')->getPattern();
391 "{$field_name}[0][value][date]" => $start_date->format($date_format),
392 "{$field_name}[0][value][time]" => $start_date->format($time_format),
393 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
394 "{$field_name}[0][end_value][time]" => $start_date->format($time_format),
397 $this->drupalPostForm(NULL, $edit, t('Save'));
398 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
400 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
402 $this->displayOptions = [
403 'type' => 'daterange_default',
406 'format_type' => 'long',
407 'separator' => 'THESEPARATOR',
408 ] + $this->defaultSettings,
411 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
412 ->setComponent($field_name, $this->displayOptions)
415 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
416 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
417 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
418 $output = $this->renderTestEntity($id);
419 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
420 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
422 // Verify that hook_entity_prepare_view can add attributes.
423 // @see entity_test_entity_prepare_view()
424 $this->drupalGet('entity_test/' . $id);
425 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
427 $this->displayOptions['type'] = 'daterange_plain';
428 $this->displayOptions['settings'] = $this->defaultSettings;
429 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
430 ->setComponent($field_name, $this->displayOptions)
432 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
433 $output = $this->renderTestEntity($id);
434 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
435 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
437 $this->displayOptions['type'] = 'daterange_custom';
438 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
439 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
440 ->setComponent($field_name, $this->displayOptions)
442 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
443 $output = $this->renderTestEntity($id);
444 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
445 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
449 * Tests all-day field.
451 public function testAlldayRangeField() {
452 $field_name = $this->fieldStorage->getName();
453 $field_label = $this->field->label();
455 // Ensure field is set to a all-day field.
456 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
457 $this->fieldStorage->save();
459 // Display creation form.
460 $this->drupalGet('entity_test/add');
461 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
462 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
463 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
464 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
465 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
466 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
467 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
468 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
470 // Build up dates in the proper timezone.
471 $value = '2012-12-31 00:00:00';
472 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
473 $end_value = '2013-06-06 23:59:59';
474 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
476 // Submit a valid date and ensure it is accepted.
477 $date_format = DateFormat::load('html_date')->getPattern();
478 $time_format = DateFormat::load('html_time')->getPattern();
481 "{$field_name}[0][value][date]" => $start_date->format($date_format),
482 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
484 $this->drupalPostForm(NULL, $edit, t('Save'));
485 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
487 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
488 $this->assertRaw($start_date->format($date_format));
489 $this->assertNoRaw($start_date->format($time_format));
490 $this->assertRaw($end_date->format($date_format));
491 $this->assertNoRaw($end_date->format($time_format));
493 // Verify that the default formatter works.
494 $this->displayOptions['settings'] = [
495 'format_type' => 'long',
496 'separator' => 'THESEPARATOR',
497 ] + $this->defaultSettings;
498 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
499 ->setComponent($field_name, $this->displayOptions)
502 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
503 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
504 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
505 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
506 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
507 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
508 $output = $this->renderTestEntity($id);
509 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
510 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
511 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
513 // Verify that hook_entity_prepare_view can add attributes.
514 // @see entity_test_entity_prepare_view()
515 $this->drupalGet('entity_test/' . $id);
516 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
518 // Verify that the plain formatter works.
519 $this->displayOptions['type'] = 'daterange_plain';
520 $this->displayOptions['settings'] = $this->defaultSettings;
521 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
522 ->setComponent($field_name, $this->displayOptions)
524 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
525 $output = $this->renderTestEntity($id);
526 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
528 // Verify that the custom formatter works.
529 $this->displayOptions['type'] = 'daterange_custom';
530 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
531 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
532 ->setComponent($field_name, $this->displayOptions)
534 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
535 $output = $this->renderTestEntity($id);
536 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
538 // Verify that the 'timezone_override' setting works.
539 $this->displayOptions['type'] = 'daterange_custom';
540 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
541 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
542 ->setComponent($field_name, $this->displayOptions)
544 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
545 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
546 $output = $this->renderTestEntity($id);
547 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
549 // Test formatters when start date and end date are the same
550 $this->drupalGet('entity_test/add');
552 $value = '2012-12-31 00:00:00';
553 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
554 $end_value = '2012-12-31 23:59:59';
555 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
557 $date_format = DateFormat::load('html_date')->getPattern();
558 $time_format = DateFormat::load('html_time')->getPattern();
561 "{$field_name}[0][value][date]" => $start_date->format($date_format),
562 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
564 $this->drupalPostForm(NULL, $edit, t('Save'));
565 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
567 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
569 $this->displayOptions = [
570 'type' => 'daterange_default',
573 'format_type' => 'long',
574 'separator' => 'THESEPARATOR',
575 ] + $this->defaultSettings,
578 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
579 ->setComponent($field_name, $this->displayOptions)
582 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
583 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
584 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
585 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
586 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
587 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
588 $output = $this->renderTestEntity($id);
589 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
590 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
591 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
593 // Verify that hook_entity_prepare_view can add attributes.
594 // @see entity_test_entity_prepare_view()
595 $this->drupalGet('entity_test/' . $id);
596 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
598 $this->displayOptions['type'] = 'daterange_plain';
599 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
600 ->setComponent($field_name, $this->displayOptions)
602 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' THESEPARATOR ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
603 $output = $this->renderTestEntity($id);
604 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
605 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
607 $this->displayOptions['type'] = 'daterange_custom';
608 $this->displayOptions['settings']['date_format'] = 'm/d/Y';
609 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
610 ->setComponent($field_name, $this->displayOptions)
612 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' THESEPARATOR ' . $end_date->format($this->displayOptions['settings']['date_format']);
613 $output = $this->renderTestEntity($id);
614 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
615 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
620 * Tests Date Range List Widget functionality.
622 public function testDatelistWidget() {
623 $field_name = $this->fieldStorage->getName();
624 $field_label = $this->field->label();
626 // Ensure field is set to a date only field.
627 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
628 $this->fieldStorage->save();
630 // Change the widget to a datelist widget.
631 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
632 ->setComponent($field_name, [
633 'type' => 'daterange_datelist',
635 'date_order' => 'YMD',
639 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
641 // Display creation form.
642 $this->drupalGet('entity_test/add');
643 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
644 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
645 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
647 // Assert that Hour and Minute Elements do not appear on Date Only.
648 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
649 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
650 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
651 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
653 // Go to the form display page to assert that increment option does not
654 // appear on Date Only.
655 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
656 $this->drupalGet($fieldEditUrl);
658 // Click on the widget settings button to open the widget settings form.
659 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
660 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
661 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
663 // Change the field is set to an all day field.
664 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
665 $this->fieldStorage->save();
667 // Change the widget to a datelist widget.
668 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
669 ->setComponent($field_name, [
670 'type' => 'daterange_datelist',
672 'date_order' => 'YMD',
676 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
678 // Display creation form.
679 $this->drupalGet('entity_test/add');
681 // Assert that Hour and Minute Elements do not appear on Date Only.
682 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
683 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
684 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
685 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
687 // Go to the form display page to assert that increment option does not
688 // appear on Date Only.
689 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
690 $this->drupalGet($fieldEditUrl);
692 // Click on the widget settings button to open the widget settings form.
693 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
694 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
695 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
697 // Change the field to a datetime field.
698 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
699 $this->fieldStorage->save();
701 // Change the widget to a datelist widget.
702 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
703 ->setComponent($field_name, [
704 'type' => 'daterange_datelist',
707 'date_order' => 'YMD',
712 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
714 // Go to the form display page to assert that increment option does appear
716 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
717 $this->drupalGet($fieldEditUrl);
719 // Click on the widget settings button to open the widget settings form.
720 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
721 $this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.');
723 // Display creation form.
724 $this->drupalGet('entity_test/add');
726 foreach (['value', 'end-value'] as $column) {
727 foreach (['year', 'month', 'day', 'hour', 'minute', 'ampm'] as $element) {
728 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-$column-$element\"]", NULL, $element . ' element found.');
729 $this->assertOptionSelected("edit-$field_name-0-$column-$element", '', 'No ' . $element . ' selected.');
733 // Submit a valid date and ensure it is accepted.
734 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15];
735 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
738 // Add the ampm indicator since we are testing 12 hour time.
739 $start_date_value['ampm'] = 'am';
740 $end_date_value['ampm'] = 'pm';
741 foreach ($start_date_value as $part => $value) {
742 $edit["{$field_name}[0][value][$part]"] = $value;
744 foreach ($end_date_value as $part => $value) {
745 $edit["{$field_name}[0][end_value][$part]"] = $value;
748 $this->drupalPostForm(NULL, $edit, t('Save'));
749 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
751 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
753 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
754 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
755 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
756 $this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.');
757 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
758 $this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.');
760 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
761 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
762 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
763 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
764 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
765 $this->assertOptionSelected("edit-$field_name-0-end-value-ampm", 'pm', 'Correct ampm selected.');
767 // Test the widget using increment other than 1 and 24 hour mode.
768 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
769 ->setComponent($field_name, [
770 'type' => 'daterange_datelist',
773 'date_order' => 'YMD',
778 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
780 // Display creation form.
781 $this->drupalGet('entity_test/add');
783 // Other elements are unaffected by the changed settings.
784 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
785 $this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
786 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.');
787 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element found.');
788 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '', 'No hour selected.');
789 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-ampm\"]", NULL, 'AMPM element not found.');
791 // Submit a valid date and ensure it is accepted.
792 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15];
793 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
796 foreach ($start_date_value as $part => $value) {
797 $edit["{$field_name}[0][value][$part]"] = $value;
799 foreach ($end_date_value as $part => $value) {
800 $edit["{$field_name}[0][end_value][$part]"] = $value;
803 $this->drupalPostForm(NULL, $edit, t('Save'));
804 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
806 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
808 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
809 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
810 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
811 $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
812 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
814 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
815 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
816 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
817 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
818 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
820 // Test the widget for partial completion of fields.
821 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
822 ->setComponent($field_name, [
823 'type' => 'daterange_datelist',
826 'date_order' => 'YMD',
831 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
833 // Test the widget for validation notifications.
834 foreach ($this->datelistDataProvider() as $data) {
835 list($start_date_value, $end_date_value, $expected) = $data;
837 // Display creation form.
838 $this->drupalGet('entity_test/add');
840 // Submit a partial date and ensure and error message is provided.
842 foreach ($start_date_value as $part => $value) {
843 $edit["{$field_name}[0][value][$part]"] = $value;
845 foreach ($end_date_value as $part => $value) {
846 $edit["{$field_name}[0][end_value][$part]"] = $value;
849 $this->drupalPostForm(NULL, $edit, t('Save'));
850 $this->assertResponse(200);
851 foreach ($expected as $expected_text) {
852 $this->assertText(t($expected_text));
856 // Test the widget for complete input with zeros as part of selections.
857 $this->drupalGet('entity_test/add');
859 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
860 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
862 foreach ($start_date_value as $part => $value) {
863 $edit["{$field_name}[0][value][$part]"] = $value;
865 foreach ($end_date_value as $part => $value) {
866 $edit["{$field_name}[0][end_value][$part]"] = $value;
869 $this->drupalPostForm(NULL, $edit, t('Save'));
870 $this->assertResponse(200);
871 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
873 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
875 // Test the widget to ensure zeros are not deselected on validation.
876 $this->drupalGet('entity_test/add');
878 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
879 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 0];
881 foreach ($start_date_value as $part => $value) {
882 $edit["{$field_name}[0][value][$part]"] = $value;
884 foreach ($end_date_value as $part => $value) {
885 $edit["{$field_name}[0][end_value][$part]"] = $value;
888 $this->drupalPostForm(NULL, $edit, t('Save'));
889 $this->assertResponse(200);
890 $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
891 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '0', 'Correct minute selected.');
895 * The data provider for testing the validation of the datelist widget.
898 * An array of datelist input permutations to test.
900 protected function datelistDataProvider() {
902 // Year only selected, validation error on Month, Day, Hour, Minute.
904 ['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''],
905 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
906 'A value must be selected for month.',
907 'A value must be selected for day.',
908 'A value must be selected for hour.',
909 'A value must be selected for minute.',
912 // Year and Month selected, validation error on Day, Hour, Minute.
914 ['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''],
915 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
916 'A value must be selected for day.',
917 'A value must be selected for hour.',
918 'A value must be selected for minute.',
921 // Year, Month and Day selected, validation error on Hour, Minute.
923 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''],
924 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
925 'A value must be selected for hour.',
926 'A value must be selected for minute.',
929 // Year, Month, Day and Hour selected, validation error on Minute only.
931 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''],
932 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
933 'A value must be selected for minute.',
936 // Year selected, validation error on Month, Day, Hour, Minute.
938 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
939 ['year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
940 'A value must be selected for month.',
941 'A value must be selected for day.',
942 'A value must be selected for hour.',
943 'A value must be selected for minute.',
946 // Year and Month selected, validation error on Day, Hour, Minute.
948 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
949 ['year' => 2013, 'month' => '1', 'day' => '', 'hour' => '', 'minute' => ''], [
950 'A value must be selected for day.',
951 'A value must be selected for hour.',
952 'A value must be selected for minute.',
955 // Year, Month and Day selected, validation error on Hour, Minute.
957 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
958 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '', 'minute' => ''], [
959 'A value must be selected for hour.',
960 'A value must be selected for minute.',
963 // Year, Month, Day and Hour selected, validation error on Minute only.
965 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
966 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => ''], [
967 'A value must be selected for minute.',
974 * Test default value functionality.
976 public function testDefaultValue() {
977 // Create a test content type.
978 $this->drupalCreateContentType(['type' => 'date_content']);
980 // Create a field storage with settings to validate.
981 $field_name = Unicode::strtolower($this->randomMachineName());
982 $field_storage = FieldStorageConfig::create([
983 'field_name' => $field_name,
984 'entity_type' => 'node',
985 'type' => 'daterange',
986 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
988 $field_storage->save();
990 $field = FieldConfig::create([
991 'field_storage' => $field_storage,
992 'bundle' => 'date_content',
996 // Set now as default_value.
998 'default_value_input[default_date_type]' => 'now',
999 'default_value_input[default_end_date_type]' => 'now',
1001 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1003 // Check that default value is selected in default value form.
1004 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1005 $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default start value is selected in instance settings page');
1006 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative start default value is empty in instance settings page');
1007 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'now', 'The default end value is selected in instance settings page');
1008 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative end default value is empty in instance settings page');
1010 // Check if default_date has been stored successfully.
1011 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1012 $this->assertEqual($config_entity['default_value'][0], [
1013 'default_date_type' => 'now',
1014 'default_date' => 'now',
1015 'default_end_date_type' => 'now',
1016 'default_end_date' => 'now',
1017 ], 'Default value has been stored successfully');
1019 // Clear field cache in order to avoid stale cache values.
1020 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1022 // Create a new node to check that datetime field default value is today.
1023 $new_node = Node::create(['type' => 'date_content']);
1024 $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1025 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1026 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1028 // Set an invalid relative default_value to test validation.
1030 'default_value_input[default_date_type]' => 'relative',
1031 'default_value_input[default_date]' => 'invalid date',
1032 'default_value_input[default_end_date_type]' => 'relative',
1033 'default_value_input[default_end_date]' => '+1 day',
1035 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1036 $this->assertText('The relative start date value entered is invalid.');
1039 'default_value_input[default_date_type]' => 'relative',
1040 'default_value_input[default_date]' => '+1 day',
1041 'default_value_input[default_end_date_type]' => 'relative',
1042 'default_value_input[default_end_date]' => 'invalid date',
1044 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1045 $this->assertText('The relative end date value entered is invalid.');
1047 // Set a relative default_value.
1049 'default_value_input[default_date_type]' => 'relative',
1050 'default_value_input[default_date]' => '+45 days',
1051 'default_value_input[default_end_date_type]' => 'relative',
1052 'default_value_input[default_end_date]' => '+90 days',
1054 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1056 // Check that default value is selected in default value form.
1057 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1058 $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default start value is selected in instance settings page');
1059 $this->assertFieldByName('default_value_input[default_date]', '+45 days', 'The relative default start value is displayed in instance settings page');
1060 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'relative', 'The default end value is selected in instance settings page');
1061 $this->assertFieldByName('default_value_input[default_end_date]', '+90 days', 'The relative default end value is displayed in instance settings page');
1063 // Check if default_date has been stored successfully.
1064 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1065 $this->assertEqual($config_entity['default_value'][0], [
1066 'default_date_type' => 'relative',
1067 'default_date' => '+45 days',
1068 'default_end_date_type' => 'relative',
1069 'default_end_date' => '+90 days',
1070 ], 'Default value has been stored successfully');
1072 // Clear field cache in order to avoid stale cache values.
1073 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1075 // Create a new node to check that datetime field default value is +90 days.
1076 $new_node = Node::create(['type' => 'date_content']);
1077 $expected_start_date = new DrupalDateTime('+45 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1078 $expected_end_date = new DrupalDateTime('+90 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1079 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1080 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1082 // Remove default value.
1084 'default_value_input[default_date_type]' => '',
1085 'default_value_input[default_end_date_type]' => '',
1087 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1089 // Check that default value is selected in default value form.
1090 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1091 $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default start value is selected in instance settings page');
1092 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default start value is empty in instance settings page');
1093 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', '', 'The default end value is selected in instance settings page');
1094 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative default end value is empty in instance settings page');
1096 // Check if default_date has been stored successfully.
1097 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1098 $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
1100 // Clear field cache in order to avoid stale cache values.
1101 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1103 // Create a new node to check that datetime field default value is not set.
1104 $new_node = Node::create(['type' => 'date_content']);
1105 $this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
1107 // Set now as default_value for start date only.
1108 entity_get_form_display('node', 'date_content', 'default')
1109 ->setComponent($field_name, [
1110 'type' => 'datetime_default',
1114 $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1117 'default_value_input[default_date_type]' => 'now',
1118 'default_value_input[default_end_date_type]' => '',
1120 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1122 // Make sure only the start value is populated on node add page.
1123 $this->drupalGet('node/add/date_content');
1124 $this->assertFieldByName("{$field_name}[0][value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'Start date element populated.');
1125 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element empty.');
1127 // Set now as default_value for end date only.
1129 'default_value_input[default_date_type]' => '',
1130 'default_value_input[default_end_date_type]' => 'now',
1132 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1134 // Make sure only the start value is populated on node add page.
1135 $this->drupalGet('node/add/date_content');
1136 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element empty.');
1137 $this->assertFieldByName("{$field_name}[0][end_value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'End date element populated.');
1141 * Test that invalid values are caught and marked as invalid.
1143 public function testInvalidField() {
1144 // Change the field to a datetime field.
1145 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
1146 $this->fieldStorage->save();
1147 $field_name = $this->fieldStorage->getName();
1148 $field_label = $this->field->label();
1150 $this->drupalGet('entity_test/add');
1151 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
1152 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
1153 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
1154 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
1156 // Submit invalid start dates and ensure they is not accepted.
1159 "{$field_name}[0][value][date]" => $date_value,
1160 "{$field_name}[0][value][time]" => '12:00:00',
1161 "{$field_name}[0][end_value][date]" => '2012-12-01',
1162 "{$field_name}[0][end_value][time]" => '12:00:00',
1164 $this->drupalPostForm(NULL, $edit, t('Save'));
1165 $this->assertText('date is invalid', 'Empty start date value has been caught.');
1167 $date_value = 'aaaa-12-01';
1169 "{$field_name}[0][value][date]" => $date_value,
1170 "{$field_name}[0][value][time]" => '00:00:00',
1171 "{$field_name}[0][end_value][date]" => '2012-12-01',
1172 "{$field_name}[0][end_value][time]" => '12:00:00',
1174 $this->drupalPostForm(NULL, $edit, t('Save'));
1175 $this->assertText('date is invalid', new FormattableMarkup('Invalid start year value %date has been caught.', ['%date' => $date_value]));
1177 $date_value = '2012-75-01';
1179 "{$field_name}[0][value][date]" => $date_value,
1180 "{$field_name}[0][value][time]" => '00:00:00',
1181 "{$field_name}[0][end_value][date]" => '2012-12-01',
1182 "{$field_name}[0][end_value][time]" => '12:00:00',
1184 $this->drupalPostForm(NULL, $edit, t('Save'));
1185 $this->assertText('date is invalid', new FormattableMarkup('Invalid start month value %date has been caught.', ['%date' => $date_value]));
1187 $date_value = '2012-12-99';
1189 "{$field_name}[0][value][date]" => $date_value,
1190 "{$field_name}[0][value][time]" => '00:00:00',
1191 "{$field_name}[0][end_value][date]" => '2012-12-01',
1192 "{$field_name}[0][end_value][time]" => '12:00:00',
1194 $this->drupalPostForm(NULL, $edit, t('Save'));
1195 $this->assertText('date is invalid', new FormattableMarkup('Invalid start day value %date has been caught.', ['%date' => $date_value]));
1197 // Submit invalid start times and ensure they is not accepted.
1200 "{$field_name}[0][value][date]" => '2012-12-01',
1201 "{$field_name}[0][value][time]" => $time_value,
1202 "{$field_name}[0][end_value][date]" => '2012-12-01',
1203 "{$field_name}[0][end_value][time]" => '12:00:00',
1205 $this->drupalPostForm(NULL, $edit, t('Save'));
1206 $this->assertText('date is invalid', 'Empty start time value has been caught.');
1208 $time_value = '49:00:00';
1210 "{$field_name}[0][value][date]" => '2012-12-01',
1211 "{$field_name}[0][value][time]" => $time_value,
1212 "{$field_name}[0][end_value][date]" => '2012-12-01',
1213 "{$field_name}[0][end_value][time]" => '12:00:00',
1215 $this->drupalPostForm(NULL, $edit, t('Save'));
1216 $this->assertText('date is invalid', new FormattableMarkup('Invalid start hour value %time has been caught.', ['%time' => $time_value]));
1218 $time_value = '12:99:00';
1220 "{$field_name}[0][value][date]" => '2012-12-01',
1221 "{$field_name}[0][value][time]" => $time_value,
1222 "{$field_name}[0][end_value][date]" => '2012-12-01',
1223 "{$field_name}[0][end_value][time]" => '12:00:00',
1225 $this->drupalPostForm(NULL, $edit, t('Save'));
1226 $this->assertText('date is invalid', new FormattableMarkup('Invalid start minute value %time has been caught.', ['%time' => $time_value]));
1228 $time_value = '12:15:99';
1230 "{$field_name}[0][value][date]" => '2012-12-01',
1231 "{$field_name}[0][value][time]" => $time_value,
1232 "{$field_name}[0][end_value][date]" => '2012-12-01',
1233 "{$field_name}[0][end_value][time]" => '12:00:00',
1235 $this->drupalPostForm(NULL, $edit, t('Save'));
1236 $this->assertText('date is invalid', new FormattableMarkup('Invalid start second value %time has been caught.', ['%time' => $time_value]));
1238 // Submit invalid end dates and ensure they is not accepted.
1241 "{$field_name}[0][value][date]" => '2012-12-01',
1242 "{$field_name}[0][value][time]" => '12:00:00',
1243 "{$field_name}[0][end_value][date]" => $date_value,
1244 "{$field_name}[0][end_value][time]" => '12:00:00',
1246 $this->drupalPostForm(NULL, $edit, t('Save'));
1247 $this->assertText('date is invalid', 'Empty end date value has been caught.');
1249 $date_value = 'aaaa-12-01';
1251 "{$field_name}[0][value][date]" => '2012-12-01',
1252 "{$field_name}[0][value][time]" => '12:00:00',
1253 "{$field_name}[0][end_value][date]" => $date_value,
1254 "{$field_name}[0][end_value][time]" => '00:00:00',
1256 $this->drupalPostForm(NULL, $edit, t('Save'));
1257 $this->assertText('date is invalid', new FormattableMarkup('Invalid end year value %date has been caught.', ['%date' => $date_value]));
1259 $date_value = '2012-75-01';
1261 "{$field_name}[0][value][date]" => '2012-12-01',
1262 "{$field_name}[0][value][time]" => '12:00:00',
1263 "{$field_name}[0][end_value][date]" => $date_value,
1264 "{$field_name}[0][end_value][time]" => '00:00:00',
1266 $this->drupalPostForm(NULL, $edit, t('Save'));
1267 $this->assertText('date is invalid', new FormattableMarkup('Invalid end month value %date has been caught.', ['%date' => $date_value]));
1269 $date_value = '2012-12-99';
1271 "{$field_name}[0][value][date]" => '2012-12-01',
1272 "{$field_name}[0][value][time]" => '12:00:00',
1273 "{$field_name}[0][end_value][date]" => $date_value,
1274 "{$field_name}[0][end_value][time]" => '00:00:00',
1276 $this->drupalPostForm(NULL, $edit, t('Save'));
1277 $this->assertText('date is invalid', new FormattableMarkup('Invalid end day value %date has been caught.', ['%date' => $date_value]));
1279 // Submit invalid start times and ensure they is not accepted.
1282 "{$field_name}[0][value][date]" => '2012-12-01',
1283 "{$field_name}[0][value][time]" => '12:00:00',
1284 "{$field_name}[0][end_value][date]" => '2012-12-01',
1285 "{$field_name}[0][end_value][time]" => $time_value,
1287 $this->drupalPostForm(NULL, $edit, t('Save'));
1288 $this->assertText('date is invalid', 'Empty end time value has been caught.');
1290 $time_value = '49:00:00';
1292 "{$field_name}[0][value][date]" => '2012-12-01',
1293 "{$field_name}[0][value][time]" => '12:00:00',
1294 "{$field_name}[0][end_value][date]" => '2012-12-01',
1295 "{$field_name}[0][end_value][time]" => $time_value,
1297 $this->drupalPostForm(NULL, $edit, t('Save'));
1298 $this->assertText('date is invalid', new FormattableMarkup('Invalid end hour value %time has been caught.', ['%time' => $time_value]));
1300 $time_value = '12:99:00';
1302 "{$field_name}[0][value][date]" => '2012-12-01',
1303 "{$field_name}[0][value][time]" => '12:00:00',
1304 "{$field_name}[0][end_value][date]" => '2012-12-01',
1305 "{$field_name}[0][end_value][time]" => $time_value,
1307 $this->drupalPostForm(NULL, $edit, t('Save'));
1308 $this->assertText('date is invalid', new FormattableMarkup('Invalid end minute value %time has been caught.', ['%time' => $time_value]));
1310 $time_value = '12:15:99';
1312 "{$field_name}[0][value][date]" => '2012-12-01',
1313 "{$field_name}[0][value][time]" => '12:00:00',
1314 "{$field_name}[0][end_value][date]" => '2012-12-01',
1315 "{$field_name}[0][end_value][time]" => $time_value,
1317 $this->drupalPostForm(NULL, $edit, t('Save'));
1318 $this->assertText('date is invalid', new FormattableMarkup('Invalid end second value %time has been caught.', ['%time' => $time_value]));
1321 "{$field_name}[0][value][date]" => '2012-12-01',
1322 "{$field_name}[0][value][time]" => '12:00:00',
1323 "{$field_name}[0][end_value][date]" => '2010-12-01',
1324 "{$field_name}[0][end_value][time]" => '12:00:00',
1326 $this->drupalPostForm(NULL, $edit, t('Save'));
1327 $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.');
1330 "{$field_name}[0][value][date]" => '2012-12-01',
1331 "{$field_name}[0][value][time]" => '12:00:00',
1332 "{$field_name}[0][end_value][date]" => '2012-12-01',
1333 "{$field_name}[0][end_value][time]" => '11:00:00',
1335 $this->drupalPostForm(NULL, $edit, t('Save'));
1336 $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.');
1340 * Tests that 'Date' field storage setting form is disabled if field has data.
1342 public function testDateStorageSettings() {
1343 // Create a test content type.
1344 $this->drupalCreateContentType(['type' => 'date_content']);
1346 // Create a field storage with settings to validate.
1347 $field_name = Unicode::strtolower($this->randomMachineName());
1348 $field_storage = FieldStorageConfig::create([
1349 'field_name' => $field_name,
1350 'entity_type' => 'node',
1351 'type' => 'daterange',
1353 'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
1356 $field_storage->save();
1357 $field = FieldConfig::create([
1358 'field_storage' => $field_storage,
1359 'field_name' => $field_name,
1360 'bundle' => 'date_content',
1364 entity_get_form_display('node', 'date_content', 'default')
1365 ->setComponent($field_name, [
1366 'type' => 'datetime_default',
1370 'title[0][value]' => $this->randomString(),
1371 'body[0][value]' => $this->randomString(),
1372 $field_name . '[0][value][date]' => '2016-04-01',
1373 $field_name . '[0][end_value][date]' => '2016-04-02',
1375 $this->drupalPostForm('node/add/date_content', $edit, t('Save'));
1376 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage');
1377 $result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]");
1378 $this->assertEqual(count($result), 1, "Changing datetime setting is disabled.");
1379 $this->assertText('There is data for this field in the database. The field settings can no longer be changed.');