Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / modules / datetime_range / tests / src / Functional / DateRangeFieldTest.php
1 <?php
2
3 namespace Drupal\Tests\datetime_range\Functional;
4
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Core\Datetime\DrupalDateTime;
7 use Drupal\Core\Datetime\Entity\DateFormat;
8 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
9 use Drupal\Tests\datetime\Functional\DateTestBase;
10 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
11 use Drupal\entity_test\Entity\EntityTest;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\field\Entity\FieldStorageConfig;
14 use Drupal\node\Entity\Node;
15
16 /**
17  * Tests Daterange field functionality.
18  *
19  * @group datetime
20  */
21 class DateRangeFieldTest extends DateTestBase {
22
23   /**
24    * Modules to enable.
25    *
26    * @var array
27    */
28   public static $modules = ['datetime_range'];
29
30   /**
31    * The default display settings to use for the formatters.
32    *
33    * @var array
34    */
35   protected $defaultSettings = ['timezone_override' => '', 'separator' => '-'];
36
37   /**
38    * {@inheritdoc}
39    */
40   protected function getTestFieldType() {
41     return 'daterange';
42   }
43
44   /**
45    * Tests date field functionality.
46    */
47   public function testDateRangeField() {
48     $field_name = $this->fieldStorage->getName();
49     $field_label = $this->field->label();
50
51     // Loop through defined timezones to test that date-only fields work at the
52     // extremes.
53     foreach (static::$timezones as $timezone) {
54
55       $this->setSiteTimezone($timezone);
56       $this->assertEquals($timezone, $this->config('system.date')->get('timezone.default'), 'Time zone set to ' . $timezone);
57
58       // Ensure field is set to a date-only field.
59       $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
60       $this->fieldStorage->save();
61
62       // Display creation form.
63       $this->drupalGet('entity_test/add');
64       $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
65       $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
66       $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
67       $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
68       $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
69       $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
70       $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
71       $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
72
73       // Build up dates in the UTC timezone.
74       $value = '2012-12-31 00:00:00';
75       $start_date = new DrupalDateTime($value, 'UTC');
76       $end_value = '2013-06-06 00:00:00';
77       $end_date = new DrupalDateTime($end_value, 'UTC');
78
79       // Submit a valid date and ensure it is accepted.
80       $date_format = DateFormat::load('html_date')->getPattern();
81       $time_format = DateFormat::load('html_time')->getPattern();
82
83       $edit = [
84         "{$field_name}[0][value][date]" => $start_date->format($date_format),
85         "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
86       ];
87       $this->drupalPostForm(NULL, $edit, t('Save'));
88       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
89       $id = $match[1];
90       $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
91       $this->assertRaw($start_date->format($date_format));
92       $this->assertNoRaw($start_date->format($time_format));
93       $this->assertRaw($end_date->format($date_format));
94       $this->assertNoRaw($end_date->format($time_format));
95
96       // Verify the date doesn't change when entity is edited through the form.
97       $entity = EntityTest::load($id);
98       $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
99       $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
100       $this->drupalGet('entity_test/manage/' . $id . '/edit');
101       $this->drupalPostForm(NULL, [], t('Save'));
102       $this->drupalGet('entity_test/manage/' . $id . '/edit');
103       $this->drupalPostForm(NULL, [], t('Save'));
104       $this->drupalGet('entity_test/manage/' . $id . '/edit');
105       $this->drupalPostForm(NULL, [], t('Save'));
106       $entity = EntityTest::load($id);
107       $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
108       $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
109
110       // Formats that display a time component for date-only fields will display
111       // the default time, so that is applied before calculating the expected
112       // value.
113       $this->massageTestDate($start_date);
114       $this->massageTestDate($end_date);
115
116       // Reset display options since these get changed below.
117       $this->displayOptions = [
118         'type' => 'daterange_default',
119         'label' => 'hidden',
120         'settings' => [
121           'format_type' => 'long',
122           'separator' => 'THESEPARATOR',
123         ] + $this->defaultSettings,
124       ];
125
126       // Verify that the default formatter works.
127       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
128         ->setComponent($field_name, $this->displayOptions)
129         ->save();
130
131       $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
132       $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
133       $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
134       $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
135       $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
136       $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
137       $output = $this->renderTestEntity($id);
138       $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
139         '%value' => 'long',
140         '%expected' => $start_expected,
141         '%expected_iso' => $start_expected_iso,
142         '%timezone' => $timezone,
143       ]));
144       $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
145         '%value' => 'long',
146         '%expected' => $end_expected,
147         '%expected_iso' => $end_expected_iso,
148         '%timezone' => $timezone,
149       ]));
150       $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
151
152       // Verify that hook_entity_prepare_view can add attributes.
153       // @see entity_test_entity_prepare_view()
154       $this->drupalGet('entity_test/' . $id);
155       $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
156
157       // Verify that the plain formatter works.
158       $this->displayOptions['type'] = 'daterange_plain';
159       $this->displayOptions['settings'] = $this->defaultSettings;
160       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
161         ->setComponent($field_name, $this->displayOptions)
162         ->save();
163       $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
164       $output = $this->renderTestEntity($id);
165       $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
166         '%expected' => $expected,
167         '%timezone' => $timezone,
168       ]));
169
170       // Verify that the custom formatter works.
171       $this->displayOptions['type'] = 'daterange_custom';
172       $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
173       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
174         ->setComponent($field_name, $this->displayOptions)
175         ->save();
176       $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
177       $output = $this->renderTestEntity($id);
178       $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
179         '%expected' => $expected,
180         '%timezone' => $timezone,
181       ]));
182
183       // Test that allowed markup in custom format is preserved and XSS is
184       // removed.
185       $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\\>';
186       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
187         ->setComponent($field_name, $this->displayOptions)
188         ->save();
189       $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))';
190       $output = $this->renderTestEntity($id);
191       $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
192         '%expected' => $expected,
193         '%timezone' => $timezone,
194       ]));
195
196       // Test formatters when start date and end date are the same
197       $this->drupalGet('entity_test/add');
198       $value = '2012-12-31 00:00:00';
199       $start_date = new DrupalDateTime($value, 'UTC');
200
201       $date_format = DateFormat::load('html_date')->getPattern();
202       $time_format = DateFormat::load('html_time')->getPattern();
203
204       $edit = [
205         "{$field_name}[0][value][date]" => $start_date->format($date_format),
206         "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
207       ];
208
209       $this->drupalPostForm(NULL, $edit, t('Save'));
210       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
211       $id = $match[1];
212       $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
213
214       $this->massageTestDate($start_date);
215
216       $this->displayOptions = [
217         'type' => 'daterange_default',
218         'label' => 'hidden',
219         'settings' => [
220             'format_type' => 'long',
221             'separator' => 'THESEPARATOR',
222           ] + $this->defaultSettings,
223       ];
224
225       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
226         ->setComponent($field_name, $this->displayOptions)
227         ->save();
228
229       $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
230       $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
231       $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
232       $output = $this->renderTestEntity($id);
233       $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
234         '%value' => 'long',
235         '%expected' => $start_expected,
236         '%expected_iso' => $start_expected_iso,
237         '%timezone' => $timezone,
238       ]));
239       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page in ' . $timezone);
240
241       // Verify that hook_entity_prepare_view can add attributes.
242       // @see entity_test_entity_prepare_view()
243       $this->drupalGet('entity_test/' . $id);
244       $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
245
246       $this->displayOptions['type'] = 'daterange_plain';
247       $this->displayOptions['settings'] = $this->defaultSettings;
248       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
249         ->setComponent($field_name, $this->displayOptions)
250         ->save();
251       $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
252       $output = $this->renderTestEntity($id);
253       $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
254         '%expected' => $expected,
255         '%timezone' => $timezone,
256       ]));
257       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
258
259       $this->displayOptions['type'] = 'daterange_custom';
260       $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
261       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
262         ->setComponent($field_name, $this->displayOptions)
263         ->save();
264       $expected = $start_date->format($this->displayOptions['settings']['date_format']);
265       $output = $this->renderTestEntity($id);
266       $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
267         '%expected' => $expected,
268         '%timezone' => $timezone,
269       ]));
270       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
271     }
272   }
273
274   /**
275    * Tests date and time field.
276    */
277   public function testDatetimeRangeField() {
278     $field_name = $this->fieldStorage->getName();
279     $field_label = $this->field->label();
280
281     // Ensure the field to a datetime field.
282     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
283     $this->fieldStorage->save();
284
285     // Display creation form.
286     $this->drupalGet('entity_test/add');
287     $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
288     $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
289     $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
290     $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
291     $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
292     $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
293     $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
294
295     // Build up dates in the UTC timezone.
296     $value = '2012-12-31 00:00:00';
297     $start_date = new DrupalDateTime($value, 'UTC');
298     $end_value = '2013-06-06 00:00:00';
299     $end_date = new DrupalDateTime($end_value, 'UTC');
300
301     // Update the timezone to the system default.
302     $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
303     $end_date->setTimezone(timezone_open(drupal_get_user_timezone()));
304
305     // Submit a valid date and ensure it is accepted.
306     $date_format = DateFormat::load('html_date')->getPattern();
307     $time_format = DateFormat::load('html_time')->getPattern();
308
309     $edit = [
310       "{$field_name}[0][value][date]" => $start_date->format($date_format),
311       "{$field_name}[0][value][time]" => $start_date->format($time_format),
312       "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
313       "{$field_name}[0][end_value][time]" => $end_date->format($time_format),
314     ];
315     $this->drupalPostForm(NULL, $edit, t('Save'));
316     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
317     $id = $match[1];
318     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
319     $this->assertRaw($start_date->format($date_format));
320     $this->assertRaw($start_date->format($time_format));
321     $this->assertRaw($end_date->format($date_format));
322     $this->assertRaw($end_date->format($time_format));
323
324     // Verify that the default formatter works.
325     $this->displayOptions['settings'] = [
326       'format_type' => 'long',
327       'separator' => 'THESEPARATOR',
328     ] + $this->defaultSettings;
329     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
330       ->setComponent($field_name, $this->displayOptions)
331       ->save();
332
333     $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
334     $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
335     $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
336     $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
337     $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
338     $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
339     $output = $this->renderTestEntity($id);
340     $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]));
341     $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]));
342     $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
343
344     // Verify that hook_entity_prepare_view can add attributes.
345     // @see entity_test_entity_prepare_view()
346     $this->drupalGet('entity_test/' . $id);
347     $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
348
349     // Verify that the plain formatter works.
350     $this->displayOptions['type'] = 'daterange_plain';
351     $this->displayOptions['settings'] = $this->defaultSettings;
352     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
353       ->setComponent($field_name, $this->displayOptions)
354       ->save();
355     $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
356     $output = $this->renderTestEntity($id);
357     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
358
359     // Verify that the 'datetime_custom' formatter works.
360     $this->displayOptions['type'] = 'daterange_custom';
361     $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
362     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
363       ->setComponent($field_name, $this->displayOptions)
364       ->save();
365     $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
366     $output = $this->renderTestEntity($id);
367     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
368
369     // Verify that the 'timezone_override' setting works.
370     $this->displayOptions['type'] = 'daterange_custom';
371     $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
372     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
373       ->setComponent($field_name, $this->displayOptions)
374       ->save();
375     $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
376     $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
377     $output = $this->renderTestEntity($id);
378     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
379
380     // Test formatters when start date and end date are the same
381     $this->drupalGet('entity_test/add');
382     $value = '2012-12-31 00:00:00';
383     $start_date = new DrupalDateTime($value, 'UTC');
384     $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
385
386     $date_format = DateFormat::load('html_date')->getPattern();
387     $time_format = DateFormat::load('html_time')->getPattern();
388
389     $edit = [
390       "{$field_name}[0][value][date]" => $start_date->format($date_format),
391       "{$field_name}[0][value][time]" => $start_date->format($time_format),
392       "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
393       "{$field_name}[0][end_value][time]" => $start_date->format($time_format),
394     ];
395
396     $this->drupalPostForm(NULL, $edit, t('Save'));
397     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
398     $id = $match[1];
399     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
400
401     $this->displayOptions = [
402       'type' => 'daterange_default',
403       'label' => 'hidden',
404       'settings' => [
405         'format_type' => 'long',
406         'separator' => 'THESEPARATOR',
407       ] + $this->defaultSettings,
408     ];
409
410     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
411       ->setComponent($field_name, $this->displayOptions)
412       ->save();
413
414     $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
415     $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
416     $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
417     $output = $this->renderTestEntity($id);
418     $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]));
419     $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
420
421     // Verify that hook_entity_prepare_view can add attributes.
422     // @see entity_test_entity_prepare_view()
423     $this->drupalGet('entity_test/' . $id);
424     $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
425
426     $this->displayOptions['type'] = 'daterange_plain';
427     $this->displayOptions['settings'] = $this->defaultSettings;
428     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
429       ->setComponent($field_name, $this->displayOptions)
430       ->save();
431     $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
432     $output = $this->renderTestEntity($id);
433     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
434     $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
435
436     $this->displayOptions['type'] = 'daterange_custom';
437     $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
438     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
439       ->setComponent($field_name, $this->displayOptions)
440       ->save();
441     $expected = $start_date->format($this->displayOptions['settings']['date_format']);
442     $output = $this->renderTestEntity($id);
443     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
444     $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
445   }
446
447   /**
448    * Tests all-day field.
449    */
450   public function testAlldayRangeField() {
451     $field_name = $this->fieldStorage->getName();
452     $field_label = $this->field->label();
453
454     // Ensure field is set to a all-day field.
455     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
456     $this->fieldStorage->save();
457
458     // Display creation form.
459     $this->drupalGet('entity_test/add');
460     $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
461     $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
462     $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
463     $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
464     $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
465     $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
466     $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
467     $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
468
469     // Build up dates in the proper timezone.
470     $value = '2012-12-31 00:00:00';
471     $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
472     $end_value = '2013-06-06 23:59:59';
473     $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
474
475     // Submit a valid date and ensure it is accepted.
476     $date_format = DateFormat::load('html_date')->getPattern();
477     $time_format = DateFormat::load('html_time')->getPattern();
478
479     $edit = [
480       "{$field_name}[0][value][date]" => $start_date->format($date_format),
481       "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
482     ];
483     $this->drupalPostForm(NULL, $edit, t('Save'));
484     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
485     $id = $match[1];
486     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
487     $this->assertRaw($start_date->format($date_format));
488     $this->assertNoRaw($start_date->format($time_format));
489     $this->assertRaw($end_date->format($date_format));
490     $this->assertNoRaw($end_date->format($time_format));
491
492     // Verify that the default formatter works.
493     $this->displayOptions['settings'] = [
494       'format_type' => 'long',
495       'separator' => 'THESEPARATOR',
496     ] + $this->defaultSettings;
497     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
498       ->setComponent($field_name, $this->displayOptions)
499       ->save();
500
501     $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
502     $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
503     $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
504     $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
505     $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
506     $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
507     $output = $this->renderTestEntity($id);
508     $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]));
509     $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]));
510     $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
511
512     // Verify that hook_entity_prepare_view can add attributes.
513     // @see entity_test_entity_prepare_view()
514     $this->drupalGet('entity_test/' . $id);
515     $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
516
517     // Verify that the plain formatter works.
518     $this->displayOptions['type'] = 'daterange_plain';
519     $this->displayOptions['settings'] = $this->defaultSettings;
520     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
521       ->setComponent($field_name, $this->displayOptions)
522       ->save();
523     $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
524     $output = $this->renderTestEntity($id);
525     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
526
527     // Verify that the custom formatter works.
528     $this->displayOptions['type'] = 'daterange_custom';
529     $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
530     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
531       ->setComponent($field_name, $this->displayOptions)
532       ->save();
533     $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
534     $output = $this->renderTestEntity($id);
535     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
536
537     // Verify that the 'timezone_override' setting works.
538     $this->displayOptions['type'] = 'daterange_custom';
539     $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
540     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
541       ->setComponent($field_name, $this->displayOptions)
542       ->save();
543     $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
544     $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
545     $output = $this->renderTestEntity($id);
546     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
547
548     // Test formatters when start date and end date are the same
549     $this->drupalGet('entity_test/add');
550
551     $value = '2012-12-31 00:00:00';
552     $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
553     $end_value = '2012-12-31 23:59:59';
554     $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
555
556     $date_format = DateFormat::load('html_date')->getPattern();
557     $time_format = DateFormat::load('html_time')->getPattern();
558
559     $edit = [
560       "{$field_name}[0][value][date]" => $start_date->format($date_format),
561       "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
562     ];
563     $this->drupalPostForm(NULL, $edit, t('Save'));
564     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
565     $id = $match[1];
566     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
567
568     $this->displayOptions = [
569       'type' => 'daterange_default',
570       'label' => 'hidden',
571       'settings' => [
572         'format_type' => 'long',
573         'separator' => 'THESEPARATOR',
574       ] + $this->defaultSettings,
575     ];
576
577     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
578       ->setComponent($field_name, $this->displayOptions)
579       ->save();
580
581     $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
582     $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
583     $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
584     $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
585     $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
586     $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
587     $output = $this->renderTestEntity($id);
588     $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]));
589     $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]));
590     $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
591
592     // Verify that hook_entity_prepare_view can add attributes.
593     // @see entity_test_entity_prepare_view()
594     $this->drupalGet('entity_test/' . $id);
595     $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
596
597     $this->displayOptions['type'] = 'daterange_plain';
598     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
599       ->setComponent($field_name, $this->displayOptions)
600       ->save();
601     $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' THESEPARATOR ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
602     $output = $this->renderTestEntity($id);
603     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
604     $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
605
606     $this->displayOptions['type'] = 'daterange_custom';
607     $this->displayOptions['settings']['date_format'] = 'm/d/Y';
608     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
609       ->setComponent($field_name, $this->displayOptions)
610       ->save();
611     $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' THESEPARATOR ' . $end_date->format($this->displayOptions['settings']['date_format']);
612     $output = $this->renderTestEntity($id);
613     $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
614     $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
615
616   }
617
618   /**
619    * Tests Date Range List Widget functionality.
620    */
621   public function testDatelistWidget() {
622     $field_name = $this->fieldStorage->getName();
623     $field_label = $this->field->label();
624
625     // Ensure field is set to a date only field.
626     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
627     $this->fieldStorage->save();
628
629     // Change the widget to a datelist widget.
630     entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
631       ->setComponent($field_name, [
632         'type' => 'daterange_datelist',
633         'settings' => [
634           'date_order' => 'YMD',
635         ],
636       ])
637       ->save();
638     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
639
640     // Display creation form.
641     $this->drupalGet('entity_test/add');
642     $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
643     $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
644     $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
645
646     // Assert that Hour and Minute Elements do not appear on Date Only.
647     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
648     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
649     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
650     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
651
652     // Go to the form display page to assert that increment option does not
653     // appear on Date Only.
654     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
655     $this->drupalGet($fieldEditUrl);
656
657     // Click on the widget settings button to open the widget settings form.
658     $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
659     $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
660     $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
661
662     // Change the field is set to an all day field.
663     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
664     $this->fieldStorage->save();
665
666     // Change the widget to a datelist widget.
667     entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
668       ->setComponent($field_name, [
669         'type' => 'daterange_datelist',
670         'settings' => [
671           'date_order' => 'YMD',
672         ],
673       ])
674       ->save();
675     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
676
677     // Display creation form.
678     $this->drupalGet('entity_test/add');
679
680     // Assert that Hour and Minute Elements do not appear on Date Only.
681     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
682     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
683     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
684     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
685
686     // Go to the form display page to assert that increment option does not
687     // appear on Date Only.
688     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
689     $this->drupalGet($fieldEditUrl);
690
691     // Click on the widget settings button to open the widget settings form.
692     $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
693     $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
694     $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
695
696     // Change the field to a datetime field.
697     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
698     $this->fieldStorage->save();
699
700     // Change the widget to a datelist widget.
701     entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
702       ->setComponent($field_name, [
703         'type' => 'daterange_datelist',
704         'settings' => [
705           'increment' => 1,
706           'date_order' => 'YMD',
707           'time_type' => '12',
708         ],
709       ])
710       ->save();
711     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
712
713     // Go to the form display page to assert that increment option does appear
714     // on Date Time.
715     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
716     $this->drupalGet($fieldEditUrl);
717
718     // Click on the widget settings button to open the widget settings form.
719     $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
720     $this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.');
721
722     // Display creation form.
723     $this->drupalGet('entity_test/add');
724
725     foreach (['value', 'end-value'] as $column) {
726       foreach (['year', 'month', 'day', 'hour', 'minute', 'ampm'] as $element) {
727         $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-$column-$element\"]", NULL, $element . ' element found.');
728         $this->assertOptionSelected("edit-$field_name-0-$column-$element", '', 'No ' . $element . ' selected.');
729       }
730     }
731
732     // Submit a valid date and ensure it is accepted.
733     $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15];
734     $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
735
736     $edit = [];
737     // Add the ampm indicator since we are testing 12 hour time.
738     $start_date_value['ampm'] = 'am';
739     $end_date_value['ampm'] = 'pm';
740     foreach ($start_date_value as $part => $value) {
741       $edit["{$field_name}[0][value][$part]"] = $value;
742     }
743     foreach ($end_date_value as $part => $value) {
744       $edit["{$field_name}[0][end_value][$part]"] = $value;
745     }
746
747     $this->drupalPostForm(NULL, $edit, t('Save'));
748     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
749     $id = $match[1];
750     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
751
752     $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
753     $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
754     $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
755     $this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.');
756     $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
757     $this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.');
758
759     $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
760     $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
761     $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
762     $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
763     $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
764     $this->assertOptionSelected("edit-$field_name-0-end-value-ampm", 'pm', 'Correct ampm selected.');
765
766     // Test the widget using increment other than 1 and 24 hour mode.
767     entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
768       ->setComponent($field_name, [
769         'type' => 'daterange_datelist',
770         'settings' => [
771           'increment' => 15,
772           'date_order' => 'YMD',
773           'time_type' => '24',
774         ],
775       ])
776       ->save();
777     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
778
779     // Display creation form.
780     $this->drupalGet('entity_test/add');
781
782     // Other elements are unaffected by the changed settings.
783     $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
784     $this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
785     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.');
786     $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element found.');
787     $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '', 'No hour selected.');
788     $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-ampm\"]", NULL, 'AMPM element not found.');
789
790     // Submit a valid date and ensure it is accepted.
791     $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15];
792     $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
793
794     $edit = [];
795     foreach ($start_date_value as $part => $value) {
796       $edit["{$field_name}[0][value][$part]"] = $value;
797     }
798     foreach ($end_date_value as $part => $value) {
799       $edit["{$field_name}[0][end_value][$part]"] = $value;
800     }
801
802     $this->drupalPostForm(NULL, $edit, t('Save'));
803     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
804     $id = $match[1];
805     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
806
807     $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
808     $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
809     $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
810     $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
811     $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
812
813     $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
814     $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
815     $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
816     $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
817     $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
818
819     // Test the widget for partial completion of fields.
820     entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
821       ->setComponent($field_name, [
822         'type' => 'daterange_datelist',
823         'settings' => [
824           'increment' => 1,
825           'date_order' => 'YMD',
826           'time_type' => '24',
827         ],
828       ])
829       ->save();
830     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
831
832     // Test the widget for validation notifications.
833     foreach ($this->datelistDataProvider() as $data) {
834       list($start_date_value, $end_date_value, $expected) = $data;
835
836       // Display creation form.
837       $this->drupalGet('entity_test/add');
838
839       // Submit a partial date and ensure and error message is provided.
840       $edit = [];
841       foreach ($start_date_value as $part => $value) {
842         $edit["{$field_name}[0][value][$part]"] = $value;
843       }
844       foreach ($end_date_value as $part => $value) {
845         $edit["{$field_name}[0][end_value][$part]"] = $value;
846       }
847
848       $this->drupalPostForm(NULL, $edit, t('Save'));
849       $this->assertResponse(200);
850       foreach ($expected as $expected_text) {
851         $this->assertText(t($expected_text));
852       }
853     }
854
855     // Test the widget for complete input with zeros as part of selections.
856     $this->drupalGet('entity_test/add');
857
858     $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
859     $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
860     $edit = [];
861     foreach ($start_date_value as $part => $value) {
862       $edit["{$field_name}[0][value][$part]"] = $value;
863     }
864     foreach ($end_date_value as $part => $value) {
865       $edit["{$field_name}[0][end_value][$part]"] = $value;
866     }
867
868     $this->drupalPostForm(NULL, $edit, t('Save'));
869     $this->assertResponse(200);
870     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
871     $id = $match[1];
872     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
873
874     // Test the widget to ensure zeros are not deselected on validation.
875     $this->drupalGet('entity_test/add');
876
877     $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
878     $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 0];
879     $edit = [];
880     foreach ($start_date_value as $part => $value) {
881       $edit["{$field_name}[0][value][$part]"] = $value;
882     }
883     foreach ($end_date_value as $part => $value) {
884       $edit["{$field_name}[0][end_value][$part]"] = $value;
885     }
886
887     $this->drupalPostForm(NULL, $edit, t('Save'));
888     $this->assertResponse(200);
889     $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
890     $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '0', 'Correct minute selected.');
891   }
892
893   /**
894    * The data provider for testing the validation of the datelist widget.
895    *
896    * @return array
897    *   An array of datelist input permutations to test.
898    */
899   protected function datelistDataProvider() {
900     return [
901       // Year only selected, validation error on Month, Day, Hour, Minute.
902       [
903         ['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''],
904         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
905           'A value must be selected for month.',
906           'A value must be selected for day.',
907           'A value must be selected for hour.',
908           'A value must be selected for minute.',
909         ],
910       ],
911       // Year and Month selected, validation error on Day, Hour, Minute.
912       [
913         ['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''],
914         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
915           'A value must be selected for day.',
916           'A value must be selected for hour.',
917           'A value must be selected for minute.',
918         ],
919       ],
920       // Year, Month and Day selected, validation error on Hour, Minute.
921       [
922         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''],
923         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
924           'A value must be selected for hour.',
925           'A value must be selected for minute.',
926         ],
927       ],
928       // Year, Month, Day and Hour selected, validation error on Minute only.
929       [
930         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''],
931         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
932           'A value must be selected for minute.',
933         ],
934       ],
935       // Year selected, validation error on Month, Day, Hour, Minute.
936       [
937         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
938         ['year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
939           'A value must be selected for month.',
940           'A value must be selected for day.',
941           'A value must be selected for hour.',
942           'A value must be selected for minute.',
943         ],
944       ],
945       // Year and Month selected, validation error on Day, Hour, Minute.
946       [
947         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
948         ['year' => 2013, 'month' => '1', 'day' => '', 'hour' => '', 'minute' => ''], [
949           'A value must be selected for day.',
950           'A value must be selected for hour.',
951           'A value must be selected for minute.',
952         ],
953       ],
954       // Year, Month and Day selected, validation error on Hour, Minute.
955       [
956         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
957         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '', 'minute' => ''], [
958           'A value must be selected for hour.',
959           'A value must be selected for minute.',
960         ],
961       ],
962       // Year, Month, Day and Hour selected, validation error on Minute only.
963       [
964         ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
965         ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => ''], [
966           'A value must be selected for minute.',
967         ],
968       ],
969     ];
970   }
971
972   /**
973    * Test default value functionality.
974    */
975   public function testDefaultValue() {
976     // Create a test content type.
977     $this->drupalCreateContentType(['type' => 'date_content']);
978
979     // Create a field storage with settings to validate.
980     $field_name = mb_strtolower($this->randomMachineName());
981     $field_storage = FieldStorageConfig::create([
982       'field_name' => $field_name,
983       'entity_type' => 'node',
984       'type' => 'daterange',
985       'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
986     ]);
987     $field_storage->save();
988
989     $field = FieldConfig::create([
990       'field_storage' => $field_storage,
991       'bundle' => 'date_content',
992     ]);
993     $field->save();
994
995     // Set now as default_value.
996     $field_edit = [
997       'default_value_input[default_date_type]' => 'now',
998       'default_value_input[default_end_date_type]' => 'now',
999     ];
1000     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1001
1002     // Check that default value is selected in default value form.
1003     $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1004     $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default start value is selected in instance settings page');
1005     $this->assertFieldByName('default_value_input[default_date]', '', 'The relative start default value is empty in instance settings page');
1006     $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'now', 'The default end value is selected in instance settings page');
1007     $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative end default value is empty in instance settings page');
1008
1009     // Check if default_date has been stored successfully.
1010     $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1011     $this->assertEqual($config_entity['default_value'][0], [
1012       'default_date_type' => 'now',
1013       'default_date' => 'now',
1014       'default_end_date_type' => 'now',
1015       'default_end_date' => 'now',
1016     ], 'Default value has been stored successfully');
1017
1018     // Clear field cache in order to avoid stale cache values.
1019     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1020
1021     // Create a new node to check that datetime field default value is today.
1022     $new_node = Node::create(['type' => 'date_content']);
1023     $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1024     $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1025     $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1026
1027     // Set an invalid relative default_value to test validation.
1028     $field_edit = [
1029       'default_value_input[default_date_type]' => 'relative',
1030       'default_value_input[default_date]' => 'invalid date',
1031       'default_value_input[default_end_date_type]' => 'relative',
1032       'default_value_input[default_end_date]' => '+1 day',
1033     ];
1034     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1035     $this->assertText('The relative start date value entered is invalid.');
1036
1037     $field_edit = [
1038       'default_value_input[default_date_type]' => 'relative',
1039       'default_value_input[default_date]' => '+1 day',
1040       'default_value_input[default_end_date_type]' => 'relative',
1041       'default_value_input[default_end_date]' => 'invalid date',
1042     ];
1043     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1044     $this->assertText('The relative end date value entered is invalid.');
1045
1046     // Set a relative default_value.
1047     $field_edit = [
1048       'default_value_input[default_date_type]' => 'relative',
1049       'default_value_input[default_date]' => '+45 days',
1050       'default_value_input[default_end_date_type]' => 'relative',
1051       'default_value_input[default_end_date]' => '+90 days',
1052     ];
1053     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1054
1055     // Check that default value is selected in default value form.
1056     $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1057     $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default start value is selected in instance settings page');
1058     $this->assertFieldByName('default_value_input[default_date]', '+45 days', 'The relative default start value is displayed in instance settings page');
1059     $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'relative', 'The default end value is selected in instance settings page');
1060     $this->assertFieldByName('default_value_input[default_end_date]', '+90 days', 'The relative default end value is displayed in instance settings page');
1061
1062     // Check if default_date has been stored successfully.
1063     $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1064     $this->assertEqual($config_entity['default_value'][0], [
1065       'default_date_type' => 'relative',
1066       'default_date' => '+45 days',
1067       'default_end_date_type' => 'relative',
1068       'default_end_date' => '+90 days',
1069     ], 'Default value has been stored successfully');
1070
1071     // Clear field cache in order to avoid stale cache values.
1072     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1073
1074     // Create a new node to check that datetime field default value is +90 days.
1075     $new_node = Node::create(['type' => 'date_content']);
1076     $expected_start_date = new DrupalDateTime('+45 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1077     $expected_end_date = new DrupalDateTime('+90 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1078     $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1079     $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1080
1081     // Remove default value.
1082     $field_edit = [
1083       'default_value_input[default_date_type]' => '',
1084       'default_value_input[default_end_date_type]' => '',
1085     ];
1086     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1087
1088     // Check that default value is selected in default value form.
1089     $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1090     $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default start value is selected in instance settings page');
1091     $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default start value is empty in instance settings page');
1092     $this->assertOptionSelected('edit-default-value-input-default-end-date-type', '', 'The default end value is selected in instance settings page');
1093     $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative default end value is empty in instance settings page');
1094
1095     // Check if default_date has been stored successfully.
1096     $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1097     $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
1098
1099     // Clear field cache in order to avoid stale cache values.
1100     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1101
1102     // Create a new node to check that datetime field default value is not set.
1103     $new_node = Node::create(['type' => 'date_content']);
1104     $this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
1105
1106     // Set now as default_value for start date only.
1107     entity_get_form_display('node', 'date_content', 'default')
1108       ->setComponent($field_name, [
1109         'type' => 'datetime_default',
1110       ])
1111       ->save();
1112
1113     $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1114
1115     $field_edit = [
1116       'default_value_input[default_date_type]' => 'now',
1117       'default_value_input[default_end_date_type]' => '',
1118     ];
1119     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1120
1121     // Make sure only the start value is populated on node add page.
1122     $this->drupalGet('node/add/date_content');
1123     $this->assertFieldByName("{$field_name}[0][value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'Start date element populated.');
1124     $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element empty.');
1125
1126     // Set now as default_value for end date only.
1127     $field_edit = [
1128       'default_value_input[default_date_type]' => '',
1129       'default_value_input[default_end_date_type]' => 'now',
1130     ];
1131     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1132
1133     // Make sure only the start value is populated on node add page.
1134     $this->drupalGet('node/add/date_content');
1135     $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element empty.');
1136     $this->assertFieldByName("{$field_name}[0][end_value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'End date element populated.');
1137   }
1138
1139   /**
1140    * Test that invalid values are caught and marked as invalid.
1141    */
1142   public function testInvalidField() {
1143     // Change the field to a datetime field.
1144     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
1145     $this->fieldStorage->save();
1146     $field_name = $this->fieldStorage->getName();
1147     $field_label = $this->field->label();
1148
1149     $this->drupalGet('entity_test/add');
1150     $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
1151     $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
1152     $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
1153     $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
1154
1155     // Submit invalid start dates and ensure they is not accepted.
1156     $date_value = '';
1157     $edit = [
1158       "{$field_name}[0][value][date]" => $date_value,
1159       "{$field_name}[0][value][time]" => '12:00:00',
1160       "{$field_name}[0][end_value][date]" => '2012-12-01',
1161       "{$field_name}[0][end_value][time]" => '12:00:00',
1162     ];
1163     $this->drupalPostForm(NULL, $edit, t('Save'));
1164     $this->assertText('date is invalid', 'Empty start date value has been caught.');
1165
1166     $date_value = 'aaaa-12-01';
1167     $edit = [
1168       "{$field_name}[0][value][date]" => $date_value,
1169       "{$field_name}[0][value][time]" => '00:00:00',
1170       "{$field_name}[0][end_value][date]" => '2012-12-01',
1171       "{$field_name}[0][end_value][time]" => '12:00:00',
1172     ];
1173     $this->drupalPostForm(NULL, $edit, t('Save'));
1174     $this->assertText('date is invalid', new FormattableMarkup('Invalid start year value %date has been caught.', ['%date' => $date_value]));
1175
1176     $date_value = '2012-75-01';
1177     $edit = [
1178       "{$field_name}[0][value][date]" => $date_value,
1179       "{$field_name}[0][value][time]" => '00:00:00',
1180       "{$field_name}[0][end_value][date]" => '2012-12-01',
1181       "{$field_name}[0][end_value][time]" => '12:00:00',
1182     ];
1183     $this->drupalPostForm(NULL, $edit, t('Save'));
1184     $this->assertText('date is invalid', new FormattableMarkup('Invalid start month value %date has been caught.', ['%date' => $date_value]));
1185
1186     $date_value = '2012-12-99';
1187     $edit = [
1188       "{$field_name}[0][value][date]" => $date_value,
1189       "{$field_name}[0][value][time]" => '00:00:00',
1190       "{$field_name}[0][end_value][date]" => '2012-12-01',
1191       "{$field_name}[0][end_value][time]" => '12:00:00',
1192     ];
1193     $this->drupalPostForm(NULL, $edit, t('Save'));
1194     $this->assertText('date is invalid', new FormattableMarkup('Invalid start day value %date has been caught.', ['%date' => $date_value]));
1195
1196     // Submit invalid start times and ensure they is not accepted.
1197     $time_value = '';
1198     $edit = [
1199       "{$field_name}[0][value][date]" => '2012-12-01',
1200       "{$field_name}[0][value][time]" => $time_value,
1201       "{$field_name}[0][end_value][date]" => '2012-12-01',
1202       "{$field_name}[0][end_value][time]" => '12:00:00',
1203     ];
1204     $this->drupalPostForm(NULL, $edit, t('Save'));
1205     $this->assertText('date is invalid', 'Empty start time value has been caught.');
1206
1207     $time_value = '49:00:00';
1208     $edit = [
1209       "{$field_name}[0][value][date]" => '2012-12-01',
1210       "{$field_name}[0][value][time]" => $time_value,
1211       "{$field_name}[0][end_value][date]" => '2012-12-01',
1212       "{$field_name}[0][end_value][time]" => '12:00:00',
1213     ];
1214     $this->drupalPostForm(NULL, $edit, t('Save'));
1215     $this->assertText('date is invalid', new FormattableMarkup('Invalid start hour value %time has been caught.', ['%time' => $time_value]));
1216
1217     $time_value = '12:99:00';
1218     $edit = [
1219       "{$field_name}[0][value][date]" => '2012-12-01',
1220       "{$field_name}[0][value][time]" => $time_value,
1221       "{$field_name}[0][end_value][date]" => '2012-12-01',
1222       "{$field_name}[0][end_value][time]" => '12:00:00',
1223     ];
1224     $this->drupalPostForm(NULL, $edit, t('Save'));
1225     $this->assertText('date is invalid', new FormattableMarkup('Invalid start minute value %time has been caught.', ['%time' => $time_value]));
1226
1227     $time_value = '12:15:99';
1228     $edit = [
1229       "{$field_name}[0][value][date]" => '2012-12-01',
1230       "{$field_name}[0][value][time]" => $time_value,
1231       "{$field_name}[0][end_value][date]" => '2012-12-01',
1232       "{$field_name}[0][end_value][time]" => '12:00:00',
1233     ];
1234     $this->drupalPostForm(NULL, $edit, t('Save'));
1235     $this->assertText('date is invalid', new FormattableMarkup('Invalid start second value %time has been caught.', ['%time' => $time_value]));
1236
1237     // Submit invalid end dates and ensure they is not accepted.
1238     $date_value = '';
1239     $edit = [
1240       "{$field_name}[0][value][date]" => '2012-12-01',
1241       "{$field_name}[0][value][time]" => '12:00:00',
1242       "{$field_name}[0][end_value][date]" => $date_value,
1243       "{$field_name}[0][end_value][time]" => '12:00:00',
1244     ];
1245     $this->drupalPostForm(NULL, $edit, t('Save'));
1246     $this->assertText('date is invalid', 'Empty end date value has been caught.');
1247
1248     $date_value = 'aaaa-12-01';
1249     $edit = [
1250       "{$field_name}[0][value][date]" => '2012-12-01',
1251       "{$field_name}[0][value][time]" => '12:00:00',
1252       "{$field_name}[0][end_value][date]" => $date_value,
1253       "{$field_name}[0][end_value][time]" => '00:00:00',
1254     ];
1255     $this->drupalPostForm(NULL, $edit, t('Save'));
1256     $this->assertText('date is invalid', new FormattableMarkup('Invalid end year value %date has been caught.', ['%date' => $date_value]));
1257
1258     $date_value = '2012-75-01';
1259     $edit = [
1260       "{$field_name}[0][value][date]" => '2012-12-01',
1261       "{$field_name}[0][value][time]" => '12:00:00',
1262       "{$field_name}[0][end_value][date]" => $date_value,
1263       "{$field_name}[0][end_value][time]" => '00:00:00',
1264     ];
1265     $this->drupalPostForm(NULL, $edit, t('Save'));
1266     $this->assertText('date is invalid', new FormattableMarkup('Invalid end month value %date has been caught.', ['%date' => $date_value]));
1267
1268     $date_value = '2012-12-99';
1269     $edit = [
1270       "{$field_name}[0][value][date]" => '2012-12-01',
1271       "{$field_name}[0][value][time]" => '12:00:00',
1272       "{$field_name}[0][end_value][date]" => $date_value,
1273       "{$field_name}[0][end_value][time]" => '00:00:00',
1274     ];
1275     $this->drupalPostForm(NULL, $edit, t('Save'));
1276     $this->assertText('date is invalid', new FormattableMarkup('Invalid end day value %date has been caught.', ['%date' => $date_value]));
1277
1278     // Submit invalid start times and ensure they is not accepted.
1279     $time_value = '';
1280     $edit = [
1281       "{$field_name}[0][value][date]" => '2012-12-01',
1282       "{$field_name}[0][value][time]" => '12:00:00',
1283       "{$field_name}[0][end_value][date]" => '2012-12-01',
1284       "{$field_name}[0][end_value][time]" => $time_value,
1285     ];
1286     $this->drupalPostForm(NULL, $edit, t('Save'));
1287     $this->assertText('date is invalid', 'Empty end time value has been caught.');
1288
1289     $time_value = '49:00:00';
1290     $edit = [
1291       "{$field_name}[0][value][date]" => '2012-12-01',
1292       "{$field_name}[0][value][time]" => '12:00:00',
1293       "{$field_name}[0][end_value][date]" => '2012-12-01',
1294       "{$field_name}[0][end_value][time]" => $time_value,
1295     ];
1296     $this->drupalPostForm(NULL, $edit, t('Save'));
1297     $this->assertText('date is invalid', new FormattableMarkup('Invalid end hour value %time has been caught.', ['%time' => $time_value]));
1298
1299     $time_value = '12:99:00';
1300     $edit = [
1301       "{$field_name}[0][value][date]" => '2012-12-01',
1302       "{$field_name}[0][value][time]" => '12:00:00',
1303       "{$field_name}[0][end_value][date]" => '2012-12-01',
1304       "{$field_name}[0][end_value][time]" => $time_value,
1305     ];
1306     $this->drupalPostForm(NULL, $edit, t('Save'));
1307     $this->assertText('date is invalid', new FormattableMarkup('Invalid end minute value %time has been caught.', ['%time' => $time_value]));
1308
1309     $time_value = '12:15:99';
1310     $edit = [
1311       "{$field_name}[0][value][date]" => '2012-12-01',
1312       "{$field_name}[0][value][time]" => '12:00:00',
1313       "{$field_name}[0][end_value][date]" => '2012-12-01',
1314       "{$field_name}[0][end_value][time]" => $time_value,
1315     ];
1316     $this->drupalPostForm(NULL, $edit, t('Save'));
1317     $this->assertText('date is invalid', new FormattableMarkup('Invalid end second value %time has been caught.', ['%time' => $time_value]));
1318
1319     $edit = [
1320       "{$field_name}[0][value][date]" => '2012-12-01',
1321       "{$field_name}[0][value][time]" => '12:00:00',
1322       "{$field_name}[0][end_value][date]" => '2010-12-01',
1323       "{$field_name}[0][end_value][time]" => '12:00:00',
1324     ];
1325     $this->drupalPostForm(NULL, $edit, t('Save'));
1326     $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.');
1327
1328     $edit = [
1329       "{$field_name}[0][value][date]" => '2012-12-01',
1330       "{$field_name}[0][value][time]" => '12:00:00',
1331       "{$field_name}[0][end_value][date]" => '2012-12-01',
1332       "{$field_name}[0][end_value][time]" => '11:00:00',
1333     ];
1334     $this->drupalPostForm(NULL, $edit, t('Save'));
1335     $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.');
1336   }
1337
1338   /**
1339    * Tests that 'Date' field storage setting form is disabled if field has data.
1340    */
1341   public function testDateStorageSettings() {
1342     // Create a test content type.
1343     $this->drupalCreateContentType(['type' => 'date_content']);
1344
1345     // Create a field storage with settings to validate.
1346     $field_name = mb_strtolower($this->randomMachineName());
1347     $field_storage = FieldStorageConfig::create([
1348       'field_name' => $field_name,
1349       'entity_type' => 'node',
1350       'type' => 'daterange',
1351       'settings' => [
1352         'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
1353       ],
1354     ]);
1355     $field_storage->save();
1356     $field = FieldConfig::create([
1357       'field_storage' => $field_storage,
1358       'field_name' => $field_name,
1359       'bundle' => 'date_content',
1360     ]);
1361     $field->save();
1362
1363     entity_get_form_display('node', 'date_content', 'default')
1364       ->setComponent($field_name, [
1365         'type' => 'datetime_default',
1366       ])
1367       ->save();
1368     $edit = [
1369       'title[0][value]' => $this->randomString(),
1370       'body[0][value]' => $this->randomString(),
1371       $field_name . '[0][value][date]' => '2016-04-01',
1372       $field_name . '[0][end_value][date]' => '2016-04-02',
1373     ];
1374     $this->drupalPostForm('node/add/date_content', $edit, t('Save'));
1375     $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage');
1376     $result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]");
1377     $this->assertEqual(count($result), 1, "Changing datetime setting is disabled.");
1378     $this->assertText('There is data for this field in the database. The field settings can no longer be changed.');
1379   }
1380
1381 }