bd83b980e7ebec2959e6fc6a13015c72b8c98839
[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\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;
16
17 /**
18  * Tests Daterange field functionality.
19  *
20  * @group datetime
21  */
22 class DateRangeFieldTest extends DateTestBase {
23
24   /**
25    * Modules to enable.
26    *
27    * @var array
28    */
29   public static $modules = ['datetime_range'];
30
31   /**
32    * The default display settings to use for the formatters.
33    *
34    * @var array
35    */
36   protected $defaultSettings = ['timezone_override' => '', 'separator' => '-'];
37
38   /**
39    * {@inheritdoc}
40    */
41   protected function getTestFieldType() {
42     return 'daterange';
43   }
44
45   /**
46    * Tests date field functionality.
47    */
48   public function testDateRangeField() {
49     $field_name = $this->fieldStorage->getName();
50     $field_label = $this->field->label();
51
52     // Loop through defined timezones to test that date-only fields work at the
53     // extremes.
54     foreach (static::$timezones as $timezone) {
55
56       $this->setSiteTimezone($timezone);
57       $this->assertEquals($timezone, $this->config('system.date')->get('timezone.default'), 'Time zone set to ' . $timezone);
58
59       // Ensure field is set to a date-only field.
60       $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
61       $this->fieldStorage->save();
62
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');
73
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');
79
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();
83
84       $edit = [
85         "{$field_name}[0][value][date]" => $start_date->format($date_format),
86         "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
87       ];
88       $this->drupalPostForm(NULL, $edit, t('Save'));
89       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
90       $id = $match[1];
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));
96
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);
110
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
113       // value.
114       $this->massageTestDate($start_date);
115       $this->massageTestDate($end_date);
116
117       // Reset display options since these get changed below.
118       $this->displayOptions = [
119         'type' => 'daterange_default',
120         'label' => 'hidden',
121         'settings' => [
122           'format_type' => 'long',
123           'separator' => 'THESEPARATOR',
124         ] + $this->defaultSettings,
125       ];
126
127       // Verify that the default formatter works.
128       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
129         ->setComponent($field_name, $this->displayOptions)
130         ->save();
131
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.', [
140         '%value' => 'long',
141         '%expected' => $start_expected,
142         '%expected_iso' => $start_expected_iso,
143         '%timezone' => $timezone,
144       ]));
145       $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
146         '%value' => 'long',
147         '%expected' => $end_expected,
148         '%expected_iso' => $end_expected_iso,
149         '%timezone' => $timezone,
150       ]));
151       $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
152
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"]');
157
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)
163         ->save();
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,
169       ]));
170
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)
176         ->save();
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,
182       ]));
183
184       // Test that allowed markup in custom format is preserved and XSS is
185       // removed.
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)
189         ->save();
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,
195       ]));
196
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');
201
202       $date_format = DateFormat::load('html_date')->getPattern();
203       $time_format = DateFormat::load('html_time')->getPattern();
204
205       $edit = [
206         "{$field_name}[0][value][date]" => $start_date->format($date_format),
207         "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
208       ];
209
210       $this->drupalPostForm(NULL, $edit, t('Save'));
211       preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
212       $id = $match[1];
213       $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
214
215       $this->massageTestDate($start_date);
216
217       $this->displayOptions = [
218         'type' => 'daterange_default',
219         'label' => 'hidden',
220         'settings' => [
221             'format_type' => 'long',
222             'separator' => 'THESEPARATOR',
223           ] + $this->defaultSettings,
224       ];
225
226       entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
227         ->setComponent($field_name, $this->displayOptions)
228         ->save();
229
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.', [
235         '%value' => 'long',
236         '%expected' => $start_expected,
237         '%expected_iso' => $start_expected_iso,
238         '%timezone' => $timezone,
239       ]));
240       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page in ' . $timezone);
241
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"]');
246
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)
251         ->save();
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,
257       ]));
258       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
259
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)
264         ->save();
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,
270       ]));
271       $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
272     }
273   }
274
275   /**
276    * Tests date and time field.
277    */
278   public function testDatetimeRangeField() {
279     $field_name = $this->fieldStorage->getName();
280     $field_label = $this->field->label();
281
282     // Ensure the field to a datetime field.
283     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
284     $this->fieldStorage->save();
285
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');
295
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');
301
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()));
305
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();
309
310     $edit = [
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),
315     ];
316     $this->drupalPostForm(NULL, $edit, t('Save'));
317     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
318     $id = $match[1];
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));
324
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)
332       ->save();
333
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');
344
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"]');
349
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)
355       ->save();
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]));
359
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)
365       ->save();
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]));
369
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)
375       ->save();
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]));
380
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()));
386
387     $date_format = DateFormat::load('html_date')->getPattern();
388     $time_format = DateFormat::load('html_time')->getPattern();
389
390     $edit = [
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),
395     ];
396
397     $this->drupalPostForm(NULL, $edit, t('Save'));
398     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
399     $id = $match[1];
400     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
401
402     $this->displayOptions = [
403       'type' => 'daterange_default',
404       'label' => 'hidden',
405       'settings' => [
406         'format_type' => 'long',
407         'separator' => 'THESEPARATOR',
408       ] + $this->defaultSettings,
409     ];
410
411     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
412       ->setComponent($field_name, $this->displayOptions)
413       ->save();
414
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');
421
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"]');
426
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)
431       ->save();
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');
436
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)
441       ->save();
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');
446   }
447
448   /**
449    * Tests all-day field.
450    */
451   public function testAlldayRangeField() {
452     $field_name = $this->fieldStorage->getName();
453     $field_label = $this->field->label();
454
455     // Ensure field is set to a all-day field.
456     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
457     $this->fieldStorage->save();
458
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');
469
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()));
475
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();
479
480     $edit = [
481       "{$field_name}[0][value][date]" => $start_date->format($date_format),
482       "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
483     ];
484     $this->drupalPostForm(NULL, $edit, t('Save'));
485     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
486     $id = $match[1];
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));
492
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)
500       ->save();
501
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');
512
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"]');
517
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)
523       ->save();
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]));
527
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)
533       ->save();
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]));
537
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)
543       ->save();
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]));
548
549     // Test formatters when start date and end date are the same
550     $this->drupalGet('entity_test/add');
551
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()));
556
557     $date_format = DateFormat::load('html_date')->getPattern();
558     $time_format = DateFormat::load('html_time')->getPattern();
559
560     $edit = [
561       "{$field_name}[0][value][date]" => $start_date->format($date_format),
562       "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
563     ];
564     $this->drupalPostForm(NULL, $edit, t('Save'));
565     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
566     $id = $match[1];
567     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
568
569     $this->displayOptions = [
570       'type' => 'daterange_default',
571       'label' => 'hidden',
572       'settings' => [
573         'format_type' => 'long',
574         'separator' => 'THESEPARATOR',
575       ] + $this->defaultSettings,
576     ];
577
578     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
579       ->setComponent($field_name, $this->displayOptions)
580       ->save();
581
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');
592
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"]');
597
598     $this->displayOptions['type'] = 'daterange_plain';
599     entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
600       ->setComponent($field_name, $this->displayOptions)
601       ->save();
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');
606
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)
611       ->save();
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');
616
617   }
618
619   /**
620    * Tests Date Range List Widget functionality.
621    */
622   public function testDatelistWidget() {
623     $field_name = $this->fieldStorage->getName();
624     $field_label = $this->field->label();
625
626     // Ensure field is set to a date only field.
627     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
628     $this->fieldStorage->save();
629
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',
634         'settings' => [
635           'date_order' => 'YMD',
636         ],
637       ])
638       ->save();
639     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
640
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');
646
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.');
652
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);
657
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.');
662
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();
666
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',
671         'settings' => [
672           'date_order' => 'YMD',
673         ],
674       ])
675       ->save();
676     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
677
678     // Display creation form.
679     $this->drupalGet('entity_test/add');
680
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.');
686
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);
691
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.');
696
697     // Change the field to a datetime field.
698     $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
699     $this->fieldStorage->save();
700
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',
705         'settings' => [
706           'increment' => 1,
707           'date_order' => 'YMD',
708           'time_type' => '12',
709         ],
710       ])
711       ->save();
712     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
713
714     // Go to the form display page to assert that increment option does appear
715     // on Date Time.
716     $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
717     $this->drupalGet($fieldEditUrl);
718
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.');
722
723     // Display creation form.
724     $this->drupalGet('entity_test/add');
725
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.');
730       }
731     }
732
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];
736
737     $edit = [];
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;
743     }
744     foreach ($end_date_value as $part => $value) {
745       $edit["{$field_name}[0][end_value][$part]"] = $value;
746     }
747
748     $this->drupalPostForm(NULL, $edit, t('Save'));
749     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
750     $id = $match[1];
751     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
752
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.');
759
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.');
766
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',
771         'settings' => [
772           'increment' => 15,
773           'date_order' => 'YMD',
774           'time_type' => '24',
775         ],
776       ])
777       ->save();
778     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
779
780     // Display creation form.
781     $this->drupalGet('entity_test/add');
782
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.');
790
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];
794
795     $edit = [];
796     foreach ($start_date_value as $part => $value) {
797       $edit["{$field_name}[0][value][$part]"] = $value;
798     }
799     foreach ($end_date_value as $part => $value) {
800       $edit["{$field_name}[0][end_value][$part]"] = $value;
801     }
802
803     $this->drupalPostForm(NULL, $edit, t('Save'));
804     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
805     $id = $match[1];
806     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
807
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.');
813
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.');
819
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',
824         'settings' => [
825           'increment' => 1,
826           'date_order' => 'YMD',
827           'time_type' => '24',
828         ],
829       ])
830       ->save();
831     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
832
833     // Test the widget for validation notifications.
834     foreach ($this->datelistDataProvider() as $data) {
835       list($start_date_value, $end_date_value, $expected) = $data;
836
837       // Display creation form.
838       $this->drupalGet('entity_test/add');
839
840       // Submit a partial date and ensure and error message is provided.
841       $edit = [];
842       foreach ($start_date_value as $part => $value) {
843         $edit["{$field_name}[0][value][$part]"] = $value;
844       }
845       foreach ($end_date_value as $part => $value) {
846         $edit["{$field_name}[0][end_value][$part]"] = $value;
847       }
848
849       $this->drupalPostForm(NULL, $edit, t('Save'));
850       $this->assertResponse(200);
851       foreach ($expected as $expected_text) {
852         $this->assertText(t($expected_text));
853       }
854     }
855
856     // Test the widget for complete input with zeros as part of selections.
857     $this->drupalGet('entity_test/add');
858
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];
861     $edit = [];
862     foreach ($start_date_value as $part => $value) {
863       $edit["{$field_name}[0][value][$part]"] = $value;
864     }
865     foreach ($end_date_value as $part => $value) {
866       $edit["{$field_name}[0][end_value][$part]"] = $value;
867     }
868
869     $this->drupalPostForm(NULL, $edit, t('Save'));
870     $this->assertResponse(200);
871     preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
872     $id = $match[1];
873     $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
874
875     // Test the widget to ensure zeros are not deselected on validation.
876     $this->drupalGet('entity_test/add');
877
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];
880     $edit = [];
881     foreach ($start_date_value as $part => $value) {
882       $edit["{$field_name}[0][value][$part]"] = $value;
883     }
884     foreach ($end_date_value as $part => $value) {
885       $edit["{$field_name}[0][end_value][$part]"] = $value;
886     }
887
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.');
892   }
893
894   /**
895    * The data provider for testing the validation of the datelist widget.
896    *
897    * @return array
898    *   An array of datelist input permutations to test.
899    */
900   protected function datelistDataProvider() {
901     return [
902       // Year only selected, validation error on Month, Day, Hour, Minute.
903       [
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.',
910         ],
911       ],
912       // Year and Month selected, validation error on Day, Hour, Minute.
913       [
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.',
919         ],
920       ],
921       // Year, Month and Day selected, validation error on Hour, Minute.
922       [
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.',
927         ],
928       ],
929       // Year, Month, Day and Hour selected, validation error on Minute only.
930       [
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.',
934         ],
935       ],
936       // Year selected, validation error on Month, Day, Hour, Minute.
937       [
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.',
944         ],
945       ],
946       // Year and Month selected, validation error on Day, Hour, Minute.
947       [
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.',
953         ],
954       ],
955       // Year, Month and Day selected, validation error on Hour, Minute.
956       [
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.',
961         ],
962       ],
963       // Year, Month, Day and Hour selected, validation error on Minute only.
964       [
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.',
968         ],
969       ],
970     ];
971   }
972
973   /**
974    * Test default value functionality.
975    */
976   public function testDefaultValue() {
977     // Create a test content type.
978     $this->drupalCreateContentType(['type' => 'date_content']);
979
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],
987     ]);
988     $field_storage->save();
989
990     $field = FieldConfig::create([
991       'field_storage' => $field_storage,
992       'bundle' => 'date_content',
993     ]);
994     $field->save();
995
996     // Set now as default_value.
997     $field_edit = [
998       'default_value_input[default_date_type]' => 'now',
999       'default_value_input[default_end_date_type]' => 'now',
1000     ];
1001     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1002
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');
1009
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');
1018
1019     // Clear field cache in order to avoid stale cache values.
1020     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1021
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));
1027
1028     // Set an invalid relative default_value to test validation.
1029     $field_edit = [
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',
1034     ];
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.');
1037
1038     $field_edit = [
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',
1043     ];
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.');
1046
1047     // Set a relative default_value.
1048     $field_edit = [
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',
1053     ];
1054     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1055
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');
1062
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');
1071
1072     // Clear field cache in order to avoid stale cache values.
1073     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1074
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));
1081
1082     // Remove default value.
1083     $field_edit = [
1084       'default_value_input[default_date_type]' => '',
1085       'default_value_input[default_end_date_type]' => '',
1086     ];
1087     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1088
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');
1095
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');
1099
1100     // Clear field cache in order to avoid stale cache values.
1101     \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1102
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');
1106
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',
1111       ])
1112       ->save();
1113
1114     $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1115
1116     $field_edit = [
1117       'default_value_input[default_date_type]' => 'now',
1118       'default_value_input[default_end_date_type]' => '',
1119     ];
1120     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1121
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.');
1126
1127     // Set now as default_value for end date only.
1128     $field_edit = [
1129       'default_value_input[default_date_type]' => '',
1130       'default_value_input[default_end_date_type]' => 'now',
1131     ];
1132     $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1133
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.');
1138   }
1139
1140   /**
1141    * Test that invalid values are caught and marked as invalid.
1142    */
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();
1149
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.');
1155
1156     // Submit invalid start dates and ensure they is not accepted.
1157     $date_value = '';
1158     $edit = [
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',
1163     ];
1164     $this->drupalPostForm(NULL, $edit, t('Save'));
1165     $this->assertText('date is invalid', 'Empty start date value has been caught.');
1166
1167     $date_value = 'aaaa-12-01';
1168     $edit = [
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',
1173     ];
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]));
1176
1177     $date_value = '2012-75-01';
1178     $edit = [
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',
1183     ];
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]));
1186
1187     $date_value = '2012-12-99';
1188     $edit = [
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',
1193     ];
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]));
1196
1197     // Submit invalid start times and ensure they is not accepted.
1198     $time_value = '';
1199     $edit = [
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',
1204     ];
1205     $this->drupalPostForm(NULL, $edit, t('Save'));
1206     $this->assertText('date is invalid', 'Empty start time value has been caught.');
1207
1208     $time_value = '49:00:00';
1209     $edit = [
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',
1214     ];
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]));
1217
1218     $time_value = '12:99:00';
1219     $edit = [
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',
1224     ];
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]));
1227
1228     $time_value = '12:15:99';
1229     $edit = [
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',
1234     ];
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]));
1237
1238     // Submit invalid end dates and ensure they is not accepted.
1239     $date_value = '';
1240     $edit = [
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',
1245     ];
1246     $this->drupalPostForm(NULL, $edit, t('Save'));
1247     $this->assertText('date is invalid', 'Empty end date value has been caught.');
1248
1249     $date_value = 'aaaa-12-01';
1250     $edit = [
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',
1255     ];
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]));
1258
1259     $date_value = '2012-75-01';
1260     $edit = [
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',
1265     ];
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]));
1268
1269     $date_value = '2012-12-99';
1270     $edit = [
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',
1275     ];
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]));
1278
1279     // Submit invalid start times and ensure they is not accepted.
1280     $time_value = '';
1281     $edit = [
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,
1286     ];
1287     $this->drupalPostForm(NULL, $edit, t('Save'));
1288     $this->assertText('date is invalid', 'Empty end time value has been caught.');
1289
1290     $time_value = '49:00:00';
1291     $edit = [
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,
1296     ];
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]));
1299
1300     $time_value = '12:99:00';
1301     $edit = [
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,
1306     ];
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]));
1309
1310     $time_value = '12:15:99';
1311     $edit = [
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,
1316     ];
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]));
1319
1320     $edit = [
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',
1325     ];
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.');
1328
1329     $edit = [
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',
1334     ];
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.');
1337   }
1338
1339   /**
1340    * Tests that 'Date' field storage setting form is disabled if field has data.
1341    */
1342   public function testDateStorageSettings() {
1343     // Create a test content type.
1344     $this->drupalCreateContentType(['type' => 'date_content']);
1345
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',
1352       'settings' => [
1353         'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
1354       ],
1355     ]);
1356     $field_storage->save();
1357     $field = FieldConfig::create([
1358       'field_storage' => $field_storage,
1359       'field_name' => $field_name,
1360       'bundle' => 'date_content',
1361     ]);
1362     $field->save();
1363
1364     entity_get_form_display('node', 'date_content', 'default')
1365       ->setComponent($field_name, [
1366         'type' => 'datetime_default',
1367       ])
1368       ->save();
1369     $edit = [
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',
1374     ];
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.');
1380   }
1381
1382 }