3 namespace Drupal\Tests\datetime_range\Functional;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Unicode;
7 use Drupal\Core\Datetime\DrupalDateTime;
8 use Drupal\Core\Datetime\Entity\DateFormat;
9 use Drupal\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;
17 * Tests Daterange field functionality.
21 class DateRangeFieldTest extends DateTestBase {
28 public static $modules = ['datetime_range'];
31 * The default display settings to use for the formatters.
35 protected $defaultSettings = ['timezone_override' => '', 'separator' => '-'];
40 protected function getTestFieldType() {
45 * Tests date field functionality.
47 public function testDateRangeField() {
48 $field_name = $this->fieldStorage->getName();
50 // Loop through defined timezones to test that date-only fields work at the
52 foreach (static::$timezones as $timezone) {
54 $this->setSiteTimezone($timezone);
56 // Ensure field is set to a date-only field.
57 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
58 $this->fieldStorage->save();
60 // Display creation form.
61 $this->drupalGet('entity_test/add');
62 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
63 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
64 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
65 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
66 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
67 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found');
68 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
69 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
71 // Build up dates in the UTC timezone.
72 $value = '2012-12-31 00:00:00';
73 $start_date = new DrupalDateTime($value, 'UTC');
74 $end_value = '2013-06-06 00:00:00';
75 $end_date = new DrupalDateTime($end_value, 'UTC');
77 // Submit a valid date and ensure it is accepted.
78 $date_format = DateFormat::load('html_date')->getPattern();
79 $time_format = DateFormat::load('html_time')->getPattern();
82 "{$field_name}[0][value][date]" => $start_date->format($date_format),
83 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
85 $this->drupalPostForm(NULL, $edit, t('Save'));
86 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
88 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
89 $this->assertRaw($start_date->format($date_format));
90 $this->assertNoRaw($start_date->format($time_format));
91 $this->assertRaw($end_date->format($date_format));
92 $this->assertNoRaw($end_date->format($time_format));
94 // Verify the date doesn't change when entity is edited through the form.
95 $entity = EntityTest::load($id);
96 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
97 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
98 $this->drupalGet('entity_test/manage/' . $id . '/edit');
99 $this->drupalPostForm(NULL, [], t('Save'));
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 $entity = EntityTest::load($id);
105 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
106 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
108 // Formats that display a time component for date-only fields will display
109 // the default time, so that is applied before calculating the expected
111 datetime_date_default_time($start_date);
112 datetime_date_default_time($end_date);
114 // Reset display options since these get changed below.
115 $this->displayOptions = [
116 'type' => 'daterange_default',
119 'format_type' => 'long',
120 'separator' => 'THESEPARATOR',
121 ] + $this->defaultSettings,
124 // Verify that the default formatter works.
125 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
126 ->setComponent($field_name, $this->displayOptions)
129 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE);
130 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE);
131 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
132 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE);
133 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE);
134 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
135 $output = $this->renderTestEntity($id);
136 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [
138 '%expected' => $start_expected,
139 '%expected_iso' => $start_expected_iso,
141 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [
143 '%expected' => $end_expected,
144 '%expected_iso' => $end_expected_iso,
146 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
148 // Verify that hook_entity_prepare_view can add attributes.
149 // @see entity_test_entity_prepare_view()
150 $this->drupalGet('entity_test/' . $id);
151 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
153 // Verify that the plain formatter works.
154 $this->displayOptions['type'] = 'daterange_plain';
155 $this->displayOptions['settings'] = $this->defaultSettings;
156 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
157 ->setComponent($field_name, $this->displayOptions)
159 $expected = $start_date->format(DATETIME_DATE_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATE_STORAGE_FORMAT);
160 $output = $this->renderTestEntity($id);
161 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
163 // Verify that the custom formatter works.
164 $this->displayOptions['type'] = 'daterange_custom';
165 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
166 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
167 ->setComponent($field_name, $this->displayOptions)
169 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
170 $output = $this->renderTestEntity($id);
171 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
173 // Test formatters when start date and end date are the same
174 $this->drupalGet('entity_test/add');
175 $value = '2012-12-31 00:00:00';
176 $start_date = new DrupalDateTime($value, 'UTC');
178 $date_format = DateFormat::load('html_date')->getPattern();
179 $time_format = DateFormat::load('html_time')->getPattern();
182 "{$field_name}[0][value][date]" => $start_date->format($date_format),
183 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
186 $this->drupalPostForm(NULL, $edit, t('Save'));
187 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
189 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
191 datetime_date_default_time($start_date);
193 $this->displayOptions = [
194 'type' => 'daterange_default',
197 'format_type' => 'long',
198 'separator' => 'THESEPARATOR',
199 ] + $this->defaultSettings,
202 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
203 ->setComponent($field_name, $this->displayOptions)
206 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DATETIME_STORAGE_TIMEZONE);
207 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DATETIME_STORAGE_TIMEZONE);
208 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
209 $output = $this->renderTestEntity($id);
210 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', [
212 '%expected' => $start_expected,
213 '%expected_iso' => $start_expected_iso,
215 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
217 // Verify that hook_entity_prepare_view can add attributes.
218 // @see entity_test_entity_prepare_view()
219 $this->drupalGet('entity_test/' . $id);
220 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
222 $this->displayOptions['type'] = 'daterange_plain';
223 $this->displayOptions['settings'] = $this->defaultSettings;
224 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
225 ->setComponent($field_name, $this->displayOptions)
227 $expected = $start_date->format(DATETIME_DATE_STORAGE_FORMAT);
228 $output = $this->renderTestEntity($id);
229 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
230 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
232 $this->displayOptions['type'] = 'daterange_custom';
233 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
234 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
235 ->setComponent($field_name, $this->displayOptions)
237 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
238 $output = $this->renderTestEntity($id);
239 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
240 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
245 * Tests date and time field.
247 public function testDatetimeRangeField() {
248 $field_name = $this->fieldStorage->getName();
250 // Ensure the field to a datetime field.
251 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
252 $this->fieldStorage->save();
254 // Display creation form.
255 $this->drupalGet('entity_test/add');
256 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
257 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
258 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
259 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
260 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found');
261 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
262 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
264 // Build up dates in the UTC timezone.
265 $value = '2012-12-31 00:00:00';
266 $start_date = new DrupalDateTime($value, 'UTC');
267 $end_value = '2013-06-06 00:00:00';
268 $end_date = new DrupalDateTime($end_value, 'UTC');
270 // Update the timezone to the system default.
271 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
272 $end_date->setTimezone(timezone_open(drupal_get_user_timezone()));
274 // Submit a valid date and ensure it is accepted.
275 $date_format = DateFormat::load('html_date')->getPattern();
276 $time_format = DateFormat::load('html_time')->getPattern();
279 "{$field_name}[0][value][date]" => $start_date->format($date_format),
280 "{$field_name}[0][value][time]" => $start_date->format($time_format),
281 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
282 "{$field_name}[0][end_value][time]" => $end_date->format($time_format),
284 $this->drupalPostForm(NULL, $edit, t('Save'));
285 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
287 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
288 $this->assertRaw($start_date->format($date_format));
289 $this->assertRaw($start_date->format($time_format));
290 $this->assertRaw($end_date->format($date_format));
291 $this->assertRaw($end_date->format($time_format));
293 // Verify that the default formatter works.
294 $this->displayOptions['settings'] = [
295 'format_type' => 'long',
296 'separator' => 'THESEPARATOR',
297 ] + $this->defaultSettings;
298 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
299 ->setComponent($field_name, $this->displayOptions)
302 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
303 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
304 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
305 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
306 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
307 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
308 $output = $this->renderTestEntity($id);
309 $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]));
310 $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]));
311 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
313 // Verify that hook_entity_prepare_view can add attributes.
314 // @see entity_test_entity_prepare_view()
315 $this->drupalGet('entity_test/' . $id);
316 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
318 // Verify that the plain formatter works.
319 $this->displayOptions['type'] = 'daterange_plain';
320 $this->displayOptions['settings'] = $this->defaultSettings;
321 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
322 ->setComponent($field_name, $this->displayOptions)
324 $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT);
325 $output = $this->renderTestEntity($id);
326 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
328 // Verify that the 'datetime_custom' formatter works.
329 $this->displayOptions['type'] = 'daterange_custom';
330 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
331 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
332 ->setComponent($field_name, $this->displayOptions)
334 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
335 $output = $this->renderTestEntity($id);
336 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
338 // Verify that the 'timezone_override' setting works.
339 $this->displayOptions['type'] = 'daterange_custom';
340 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
341 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
342 ->setComponent($field_name, $this->displayOptions)
344 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
345 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
346 $output = $this->renderTestEntity($id);
347 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
349 // Test formatters when start date and end date are the same
350 $this->drupalGet('entity_test/add');
351 $value = '2012-12-31 00:00:00';
352 $start_date = new DrupalDateTime($value, 'UTC');
353 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
355 $date_format = DateFormat::load('html_date')->getPattern();
356 $time_format = DateFormat::load('html_time')->getPattern();
359 "{$field_name}[0][value][date]" => $start_date->format($date_format),
360 "{$field_name}[0][value][time]" => $start_date->format($time_format),
361 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
362 "{$field_name}[0][end_value][time]" => $start_date->format($time_format),
365 $this->drupalPostForm(NULL, $edit, t('Save'));
366 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
368 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
370 $this->displayOptions = [
371 'type' => 'daterange_default',
374 'format_type' => 'long',
375 'separator' => 'THESEPARATOR',
376 ] + $this->defaultSettings,
379 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
380 ->setComponent($field_name, $this->displayOptions)
383 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
384 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
385 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
386 $output = $this->renderTestEntity($id);
387 $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]));
388 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
390 // Verify that hook_entity_prepare_view can add attributes.
391 // @see entity_test_entity_prepare_view()
392 $this->drupalGet('entity_test/' . $id);
393 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
395 $this->displayOptions['type'] = 'daterange_plain';
396 $this->displayOptions['settings'] = $this->defaultSettings;
397 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
398 ->setComponent($field_name, $this->displayOptions)
400 $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT);
401 $output = $this->renderTestEntity($id);
402 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
403 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
405 $this->displayOptions['type'] = 'daterange_custom';
406 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
407 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
408 ->setComponent($field_name, $this->displayOptions)
410 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
411 $output = $this->renderTestEntity($id);
412 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
413 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
417 * Tests all-day field.
419 public function testAlldayRangeField() {
420 $field_name = $this->fieldStorage->getName();
422 // Ensure field is set to a all-day field.
423 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
424 $this->fieldStorage->save();
426 // Display creation form.
427 $this->drupalGet('entity_test/add');
428 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
429 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
430 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
431 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
432 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
433 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found');
434 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
435 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
437 // Build up dates in the proper timezone.
438 $value = '2012-12-31 00:00:00';
439 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
440 $end_value = '2013-06-06 23:59:59';
441 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
443 // Submit a valid date and ensure it is accepted.
444 $date_format = DateFormat::load('html_date')->getPattern();
445 $time_format = DateFormat::load('html_time')->getPattern();
448 "{$field_name}[0][value][date]" => $start_date->format($date_format),
449 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
451 $this->drupalPostForm(NULL, $edit, t('Save'));
452 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
454 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
455 $this->assertRaw($start_date->format($date_format));
456 $this->assertNoRaw($start_date->format($time_format));
457 $this->assertRaw($end_date->format($date_format));
458 $this->assertNoRaw($end_date->format($time_format));
460 // Verify that the default formatter works.
461 $this->displayOptions['settings'] = [
462 'format_type' => 'long',
463 'separator' => 'THESEPARATOR',
464 ] + $this->defaultSettings;
465 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
466 ->setComponent($field_name, $this->displayOptions)
469 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
470 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
471 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
472 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
473 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
474 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
475 $output = $this->renderTestEntity($id);
476 $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]));
477 $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]));
478 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
480 // Verify that hook_entity_prepare_view can add attributes.
481 // @see entity_test_entity_prepare_view()
482 $this->drupalGet('entity_test/' . $id);
483 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
485 // Verify that the plain formatter works.
486 $this->displayOptions['type'] = 'daterange_plain';
487 $this->displayOptions['settings'] = $this->defaultSettings;
488 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
489 ->setComponent($field_name, $this->displayOptions)
491 $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT);
492 $output = $this->renderTestEntity($id);
493 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
495 // Verify that the custom formatter works.
496 $this->displayOptions['type'] = 'daterange_custom';
497 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
498 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
499 ->setComponent($field_name, $this->displayOptions)
501 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
502 $output = $this->renderTestEntity($id);
503 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
505 // Verify that the 'timezone_override' setting works.
506 $this->displayOptions['type'] = 'daterange_custom';
507 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
508 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
509 ->setComponent($field_name, $this->displayOptions)
511 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
512 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
513 $output = $this->renderTestEntity($id);
514 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
516 // Test formatters when start date and end date are the same
517 $this->drupalGet('entity_test/add');
519 $value = '2012-12-31 00:00:00';
520 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
521 $end_value = '2012-12-31 23:59:59';
522 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
524 $date_format = DateFormat::load('html_date')->getPattern();
525 $time_format = DateFormat::load('html_time')->getPattern();
528 "{$field_name}[0][value][date]" => $start_date->format($date_format),
529 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
531 $this->drupalPostForm(NULL, $edit, t('Save'));
532 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
534 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
536 $this->displayOptions = [
537 'type' => 'daterange_default',
540 'format_type' => 'long',
541 'separator' => 'THESEPARATOR',
542 ] + $this->defaultSettings,
545 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
546 ->setComponent($field_name, $this->displayOptions)
549 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
550 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
551 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
552 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
553 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
554 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
555 $output = $this->renderTestEntity($id);
556 $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]));
557 $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]));
558 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
560 // Verify that hook_entity_prepare_view can add attributes.
561 // @see entity_test_entity_prepare_view()
562 $this->drupalGet('entity_test/' . $id);
563 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
565 $this->displayOptions['type'] = 'daterange_plain';
566 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
567 ->setComponent($field_name, $this->displayOptions)
569 $expected = $start_date->format(DATETIME_DATETIME_STORAGE_FORMAT) . ' THESEPARATOR ' . $end_date->format(DATETIME_DATETIME_STORAGE_FORMAT);
570 $output = $this->renderTestEntity($id);
571 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
572 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
574 $this->displayOptions['type'] = 'daterange_custom';
575 $this->displayOptions['settings']['date_format'] = 'm/d/Y';
576 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
577 ->setComponent($field_name, $this->displayOptions)
579 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' THESEPARATOR ' . $end_date->format($this->displayOptions['settings']['date_format']);
580 $output = $this->renderTestEntity($id);
581 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
582 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
587 * Tests Date Range List Widget functionality.
589 public function testDatelistWidget() {
590 $field_name = $this->fieldStorage->getName();
592 // Ensure field is set to a date only field.
593 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
594 $this->fieldStorage->save();
596 // Change the widget to a datelist widget.
597 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
598 ->setComponent($field_name, [
599 'type' => 'daterange_datelist',
601 'date_order' => 'YMD',
605 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
607 // Display creation form.
608 $this->drupalGet('entity_test/add');
609 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_name, 'Fieldset and label found');
610 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
611 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
613 // Assert that Hour and Minute Elements do not appear on Date Only.
614 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
615 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
616 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
617 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
619 // Go to the form display page to assert that increment option does not
620 // appear on Date Only.
621 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
622 $this->drupalGet($fieldEditUrl);
624 // Click on the widget settings button to open the widget settings form.
625 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
626 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
627 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
629 // Change the field is set to an all day field.
630 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
631 $this->fieldStorage->save();
633 // Change the widget to a datelist widget.
634 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
635 ->setComponent($field_name, [
636 'type' => 'daterange_datelist',
638 'date_order' => 'YMD',
642 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
644 // Display creation form.
645 $this->drupalGet('entity_test/add');
647 // Assert that Hour and Minute Elements do not appear on Date Only.
648 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
649 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
650 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
651 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
653 // Go to the form display page to assert that increment option does not
654 // appear on Date Only.
655 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
656 $this->drupalGet($fieldEditUrl);
658 // Click on the widget settings button to open the widget settings form.
659 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
660 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
661 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
663 // Change the field to a datetime field.
664 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
665 $this->fieldStorage->save();
667 // Change the widget to a datelist widget.
668 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
669 ->setComponent($field_name, [
670 'type' => 'daterange_datelist',
673 'date_order' => 'YMD',
678 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
680 // Go to the form display page to assert that increment option does appear
682 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
683 $this->drupalGet($fieldEditUrl);
685 // Click on the widget settings button to open the widget settings form.
686 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
687 $this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.');
689 // Display creation form.
690 $this->drupalGet('entity_test/add');
692 foreach (['value', 'end-value'] as $column) {
693 foreach (['year', 'month', 'day', 'hour', 'minute', 'ampm'] as $element) {
694 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-$column-$element\"]", NULL, $element . ' element found.');
695 $this->assertOptionSelected("edit-$field_name-0-$column-$element", '', 'No ' . $element . ' selected.');
699 // Submit a valid date and ensure it is accepted.
700 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15];
701 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
704 // Add the ampm indicator since we are testing 12 hour time.
705 $start_date_value['ampm'] = 'am';
706 $end_date_value['ampm'] = 'pm';
707 foreach ($start_date_value as $part => $value) {
708 $edit["{$field_name}[0][value][$part]"] = $value;
710 foreach ($end_date_value as $part => $value) {
711 $edit["{$field_name}[0][end_value][$part]"] = $value;
714 $this->drupalPostForm(NULL, $edit, t('Save'));
715 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
717 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
719 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
720 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
721 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
722 $this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.');
723 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
724 $this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.');
726 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
727 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
728 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
729 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
730 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
731 $this->assertOptionSelected("edit-$field_name-0-end-value-ampm", 'pm', 'Correct ampm selected.');
733 // Test the widget using increment other than 1 and 24 hour mode.
734 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
735 ->setComponent($field_name, [
736 'type' => 'daterange_datelist',
739 'date_order' => 'YMD',
744 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
746 // Display creation form.
747 $this->drupalGet('entity_test/add');
749 // Other elements are unaffected by the changed settings.
750 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
751 $this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
752 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.');
753 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element found.');
754 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '', 'No hour selected.');
755 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-ampm\"]", NULL, 'AMPM element not found.');
757 // Submit a valid date and ensure it is accepted.
758 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15];
759 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
762 foreach ($start_date_value as $part => $value) {
763 $edit["{$field_name}[0][value][$part]"] = $value;
765 foreach ($end_date_value as $part => $value) {
766 $edit["{$field_name}[0][end_value][$part]"] = $value;
769 $this->drupalPostForm(NULL, $edit, t('Save'));
770 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
772 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
774 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
775 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
776 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
777 $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
778 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
780 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
781 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
782 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
783 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
784 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
786 // Test the widget for partial completion of fields.
787 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
788 ->setComponent($field_name, [
789 'type' => 'daterange_datelist',
792 'date_order' => 'YMD',
797 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
799 // Test the widget for validation notifications.
800 foreach ($this->datelistDataProvider() as $data) {
801 list($start_date_value, $end_date_value, $expected) = $data;
803 // Display creation form.
804 $this->drupalGet('entity_test/add');
806 // Submit a partial date and ensure and error message is provided.
808 foreach ($start_date_value as $part => $value) {
809 $edit["{$field_name}[0][value][$part]"] = $value;
811 foreach ($end_date_value as $part => $value) {
812 $edit["{$field_name}[0][end_value][$part]"] = $value;
815 $this->drupalPostForm(NULL, $edit, t('Save'));
816 $this->assertResponse(200);
817 foreach ($expected as $expected_text) {
818 $this->assertText(t($expected_text));
822 // Test the widget for complete input with zeros as part of selections.
823 $this->drupalGet('entity_test/add');
825 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
826 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
828 foreach ($start_date_value as $part => $value) {
829 $edit["{$field_name}[0][value][$part]"] = $value;
831 foreach ($end_date_value as $part => $value) {
832 $edit["{$field_name}[0][end_value][$part]"] = $value;
835 $this->drupalPostForm(NULL, $edit, t('Save'));
836 $this->assertResponse(200);
837 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
839 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
841 // Test the widget to ensure zeros are not deselected on validation.
842 $this->drupalGet('entity_test/add');
844 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
845 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 0];
847 foreach ($start_date_value as $part => $value) {
848 $edit["{$field_name}[0][value][$part]"] = $value;
850 foreach ($end_date_value as $part => $value) {
851 $edit["{$field_name}[0][end_value][$part]"] = $value;
854 $this->drupalPostForm(NULL, $edit, t('Save'));
855 $this->assertResponse(200);
856 $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
857 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '0', 'Correct minute selected.');
861 * The data provider for testing the validation of the datelist widget.
864 * An array of datelist input permutations to test.
866 protected function datelistDataProvider() {
868 // Year only selected, validation error on Month, Day, Hour, Minute.
870 ['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''],
871 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
872 'A value must be selected for month.',
873 'A value must be selected for day.',
874 'A value must be selected for hour.',
875 'A value must be selected for minute.',
878 // Year and Month selected, validation error on Day, Hour, Minute.
880 ['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''],
881 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
882 'A value must be selected for day.',
883 'A value must be selected for hour.',
884 'A value must be selected for minute.',
887 // Year, Month and Day selected, validation error on Hour, Minute.
889 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''],
890 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
891 'A value must be selected for hour.',
892 'A value must be selected for minute.',
895 // Year, Month, Day and Hour selected, validation error on Minute only.
897 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''],
898 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
899 'A value must be selected for minute.',
902 // Year selected, validation error on Month, Day, Hour, Minute.
904 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
905 ['year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
906 'A value must be selected for month.',
907 'A value must be selected for day.',
908 'A value must be selected for hour.',
909 'A value must be selected for minute.',
912 // Year and Month selected, validation error on Day, Hour, Minute.
914 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
915 ['year' => 2013, 'month' => '1', 'day' => '', 'hour' => '', 'minute' => ''], [
916 'A value must be selected for day.',
917 'A value must be selected for hour.',
918 'A value must be selected for minute.',
921 // Year, Month and Day selected, validation error on Hour, Minute.
923 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
924 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '', 'minute' => ''], [
925 'A value must be selected for hour.',
926 'A value must be selected for minute.',
929 // Year, Month, Day and Hour selected, validation error on Minute only.
931 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
932 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => ''], [
933 'A value must be selected for minute.',
940 * Test default value functionality.
942 public function testDefaultValue() {
943 // Create a test content type.
944 $this->drupalCreateContentType(['type' => 'date_content']);
946 // Create a field storage with settings to validate.
947 $field_name = Unicode::strtolower($this->randomMachineName());
948 $field_storage = FieldStorageConfig::create([
949 'field_name' => $field_name,
950 'entity_type' => 'node',
951 'type' => 'daterange',
952 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
954 $field_storage->save();
956 $field = FieldConfig::create([
957 'field_storage' => $field_storage,
958 'bundle' => 'date_content',
962 // Set now as default_value.
964 'default_value_input[default_date_type]' => 'now',
965 'default_value_input[default_end_date_type]' => 'now',
967 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
969 // Check that default value is selected in default value form.
970 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
971 $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default start value is selected in instance settings page');
972 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative start default value is empty in instance settings page');
973 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'now', 'The default end value is selected in instance settings page');
974 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative end default value is empty in instance settings page');
976 // Check if default_date has been stored successfully.
977 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
978 $this->assertEqual($config_entity['default_value'][0], [
979 'default_date_type' => 'now',
980 'default_date' => 'now',
981 'default_end_date_type' => 'now',
982 'default_end_date' => 'now',
983 ], 'Default value has been stored successfully');
985 // Clear field cache in order to avoid stale cache values.
986 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
988 // Create a new node to check that datetime field default value is today.
989 $new_node = Node::create(['type' => 'date_content']);
990 $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
991 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
992 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_date->format(DATETIME_DATE_STORAGE_FORMAT));
994 // Set an invalid relative default_value to test validation.
996 'default_value_input[default_date_type]' => 'relative',
997 'default_value_input[default_date]' => 'invalid date',
998 'default_value_input[default_end_date_type]' => 'relative',
999 'default_value_input[default_end_date]' => '+1 day',
1001 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1002 $this->assertText('The relative start date value entered is invalid.');
1005 'default_value_input[default_date_type]' => 'relative',
1006 'default_value_input[default_date]' => '+1 day',
1007 'default_value_input[default_end_date_type]' => 'relative',
1008 'default_value_input[default_end_date]' => 'invalid date',
1010 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1011 $this->assertText('The relative end date value entered is invalid.');
1013 // Set a relative default_value.
1015 'default_value_input[default_date_type]' => 'relative',
1016 'default_value_input[default_date]' => '+45 days',
1017 'default_value_input[default_end_date_type]' => 'relative',
1018 'default_value_input[default_end_date]' => '+90 days',
1020 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1022 // Check that default value is selected in default value form.
1023 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1024 $this->assertOptionSelected('edit-default-value-input-default-date-type', 'relative', 'The default start value is selected in instance settings page');
1025 $this->assertFieldByName('default_value_input[default_date]', '+45 days', 'The relative default start value is displayed in instance settings page');
1026 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'relative', 'The default end value is selected in instance settings page');
1027 $this->assertFieldByName('default_value_input[default_end_date]', '+90 days', 'The relative default end value is displayed in instance settings page');
1029 // Check if default_date has been stored successfully.
1030 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1031 $this->assertEqual($config_entity['default_value'][0], [
1032 'default_date_type' => 'relative',
1033 'default_date' => '+45 days',
1034 'default_end_date_type' => 'relative',
1035 'default_end_date' => '+90 days',
1036 ], 'Default value has been stored successfully');
1038 // Clear field cache in order to avoid stale cache values.
1039 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1041 // Create a new node to check that datetime field default value is +90 days.
1042 $new_node = Node::create(['type' => 'date_content']);
1043 $expected_start_date = new DrupalDateTime('+45 days', DATETIME_STORAGE_TIMEZONE);
1044 $expected_end_date = new DrupalDateTime('+90 days', DATETIME_STORAGE_TIMEZONE);
1045 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_start_date->format(DATETIME_DATE_STORAGE_FORMAT));
1046 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_end_date->format(DATETIME_DATE_STORAGE_FORMAT));
1048 // Remove default value.
1050 'default_value_input[default_date_type]' => '',
1051 'default_value_input[default_end_date_type]' => '',
1053 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
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', '', 'The default start value is selected in instance settings page');
1058 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default start value is empty in instance settings page');
1059 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', '', 'The default end value is selected in instance settings page');
1060 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative default end value is empty in instance settings page');
1062 // Check if default_date has been stored successfully.
1063 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1064 $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
1066 // Clear field cache in order to avoid stale cache values.
1067 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1069 // Create a new node to check that datetime field default value is not set.
1070 $new_node = Node::create(['type' => 'date_content']);
1071 $this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
1073 // Set now as default_value for start date only.
1074 entity_get_form_display('node', 'date_content', 'default')
1075 ->setComponent($field_name, [
1076 'type' => 'datetime_default',
1080 $expected_date = new DrupalDateTime('now', DATETIME_STORAGE_TIMEZONE);
1083 'default_value_input[default_date_type]' => 'now',
1084 'default_value_input[default_end_date_type]' => '',
1086 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1088 // Make sure only the start value is populated on node add page.
1089 $this->drupalGet('node/add/date_content');
1090 $this->assertFieldByName("{$field_name}[0][value][date]", $expected_date->format(DATETIME_DATE_STORAGE_FORMAT), 'Start date element populated.');
1091 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element empty.');
1093 // Set now as default_value for end date only.
1095 'default_value_input[default_date_type]' => '',
1096 'default_value_input[default_end_date_type]' => 'now',
1098 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1100 // Make sure only the start value is populated on node add page.
1101 $this->drupalGet('node/add/date_content');
1102 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element empty.');
1103 $this->assertFieldByName("{$field_name}[0][end_value][date]", $expected_date->format(DATETIME_DATE_STORAGE_FORMAT), 'End date element populated.');
1107 * Test that invalid values are caught and marked as invalid.
1109 public function testInvalidField() {
1110 // Change the field to a datetime field.
1111 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
1112 $this->fieldStorage->save();
1113 $field_name = $this->fieldStorage->getName();
1115 $this->drupalGet('entity_test/add');
1116 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
1117 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
1118 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
1119 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
1121 // Submit invalid start dates and ensure they is not accepted.
1124 "{$field_name}[0][value][date]" => $date_value,
1125 "{$field_name}[0][value][time]" => '12:00:00',
1126 "{$field_name}[0][end_value][date]" => '2012-12-01',
1127 "{$field_name}[0][end_value][time]" => '12:00:00',
1129 $this->drupalPostForm(NULL, $edit, t('Save'));
1130 $this->assertText('date is invalid', 'Empty start date value has been caught.');
1132 $date_value = 'aaaa-12-01';
1134 "{$field_name}[0][value][date]" => $date_value,
1135 "{$field_name}[0][value][time]" => '00:00:00',
1136 "{$field_name}[0][end_value][date]" => '2012-12-01',
1137 "{$field_name}[0][end_value][time]" => '12:00:00',
1139 $this->drupalPostForm(NULL, $edit, t('Save'));
1140 $this->assertText('date is invalid', new FormattableMarkup('Invalid start year value %date has been caught.', ['%date' => $date_value]));
1142 $date_value = '2012-75-01';
1144 "{$field_name}[0][value][date]" => $date_value,
1145 "{$field_name}[0][value][time]" => '00:00:00',
1146 "{$field_name}[0][end_value][date]" => '2012-12-01',
1147 "{$field_name}[0][end_value][time]" => '12:00:00',
1149 $this->drupalPostForm(NULL, $edit, t('Save'));
1150 $this->assertText('date is invalid', new FormattableMarkup('Invalid start month value %date has been caught.', ['%date' => $date_value]));
1152 $date_value = '2012-12-99';
1154 "{$field_name}[0][value][date]" => $date_value,
1155 "{$field_name}[0][value][time]" => '00:00:00',
1156 "{$field_name}[0][end_value][date]" => '2012-12-01',
1157 "{$field_name}[0][end_value][time]" => '12:00:00',
1159 $this->drupalPostForm(NULL, $edit, t('Save'));
1160 $this->assertText('date is invalid', new FormattableMarkup('Invalid start day value %date has been caught.', ['%date' => $date_value]));
1162 // Submit invalid start times and ensure they is not accepted.
1165 "{$field_name}[0][value][date]" => '2012-12-01',
1166 "{$field_name}[0][value][time]" => $time_value,
1167 "{$field_name}[0][end_value][date]" => '2012-12-01',
1168 "{$field_name}[0][end_value][time]" => '12:00:00',
1170 $this->drupalPostForm(NULL, $edit, t('Save'));
1171 $this->assertText('date is invalid', 'Empty start time value has been caught.');
1173 $time_value = '49:00:00';
1175 "{$field_name}[0][value][date]" => '2012-12-01',
1176 "{$field_name}[0][value][time]" => $time_value,
1177 "{$field_name}[0][end_value][date]" => '2012-12-01',
1178 "{$field_name}[0][end_value][time]" => '12:00:00',
1180 $this->drupalPostForm(NULL, $edit, t('Save'));
1181 $this->assertText('date is invalid', new FormattableMarkup('Invalid start hour value %time has been caught.', ['%time' => $time_value]));
1183 $time_value = '12:99:00';
1185 "{$field_name}[0][value][date]" => '2012-12-01',
1186 "{$field_name}[0][value][time]" => $time_value,
1187 "{$field_name}[0][end_value][date]" => '2012-12-01',
1188 "{$field_name}[0][end_value][time]" => '12:00:00',
1190 $this->drupalPostForm(NULL, $edit, t('Save'));
1191 $this->assertText('date is invalid', new FormattableMarkup('Invalid start minute value %time has been caught.', ['%time' => $time_value]));
1193 $time_value = '12:15:99';
1195 "{$field_name}[0][value][date]" => '2012-12-01',
1196 "{$field_name}[0][value][time]" => $time_value,
1197 "{$field_name}[0][end_value][date]" => '2012-12-01',
1198 "{$field_name}[0][end_value][time]" => '12:00:00',
1200 $this->drupalPostForm(NULL, $edit, t('Save'));
1201 $this->assertText('date is invalid', new FormattableMarkup('Invalid start second value %time has been caught.', ['%time' => $time_value]));
1203 // Submit invalid end dates and ensure they is not accepted.
1206 "{$field_name}[0][value][date]" => '2012-12-01',
1207 "{$field_name}[0][value][time]" => '12:00:00',
1208 "{$field_name}[0][end_value][date]" => $date_value,
1209 "{$field_name}[0][end_value][time]" => '12:00:00',
1211 $this->drupalPostForm(NULL, $edit, t('Save'));
1212 $this->assertText('date is invalid', 'Empty end date value has been caught.');
1214 $date_value = 'aaaa-12-01';
1216 "{$field_name}[0][value][date]" => '2012-12-01',
1217 "{$field_name}[0][value][time]" => '12:00:00',
1218 "{$field_name}[0][end_value][date]" => $date_value,
1219 "{$field_name}[0][end_value][time]" => '00:00:00',
1221 $this->drupalPostForm(NULL, $edit, t('Save'));
1222 $this->assertText('date is invalid', new FormattableMarkup('Invalid end year value %date has been caught.', ['%date' => $date_value]));
1224 $date_value = '2012-75-01';
1226 "{$field_name}[0][value][date]" => '2012-12-01',
1227 "{$field_name}[0][value][time]" => '12:00:00',
1228 "{$field_name}[0][end_value][date]" => $date_value,
1229 "{$field_name}[0][end_value][time]" => '00:00:00',
1231 $this->drupalPostForm(NULL, $edit, t('Save'));
1232 $this->assertText('date is invalid', new FormattableMarkup('Invalid end month value %date has been caught.', ['%date' => $date_value]));
1234 $date_value = '2012-12-99';
1236 "{$field_name}[0][value][date]" => '2012-12-01',
1237 "{$field_name}[0][value][time]" => '12:00:00',
1238 "{$field_name}[0][end_value][date]" => $date_value,
1239 "{$field_name}[0][end_value][time]" => '00:00:00',
1241 $this->drupalPostForm(NULL, $edit, t('Save'));
1242 $this->assertText('date is invalid', new FormattableMarkup('Invalid end day value %date has been caught.', ['%date' => $date_value]));
1244 // Submit invalid start times and ensure they is not accepted.
1247 "{$field_name}[0][value][date]" => '2012-12-01',
1248 "{$field_name}[0][value][time]" => '12:00:00',
1249 "{$field_name}[0][end_value][date]" => '2012-12-01',
1250 "{$field_name}[0][end_value][time]" => $time_value,
1252 $this->drupalPostForm(NULL, $edit, t('Save'));
1253 $this->assertText('date is invalid', 'Empty end time value has been caught.');
1255 $time_value = '49:00:00';
1257 "{$field_name}[0][value][date]" => '2012-12-01',
1258 "{$field_name}[0][value][time]" => '12:00:00',
1259 "{$field_name}[0][end_value][date]" => '2012-12-01',
1260 "{$field_name}[0][end_value][time]" => $time_value,
1262 $this->drupalPostForm(NULL, $edit, t('Save'));
1263 $this->assertText('date is invalid', new FormattableMarkup('Invalid end hour value %time has been caught.', ['%time' => $time_value]));
1265 $time_value = '12:99:00';
1267 "{$field_name}[0][value][date]" => '2012-12-01',
1268 "{$field_name}[0][value][time]" => '12:00:00',
1269 "{$field_name}[0][end_value][date]" => '2012-12-01',
1270 "{$field_name}[0][end_value][time]" => $time_value,
1272 $this->drupalPostForm(NULL, $edit, t('Save'));
1273 $this->assertText('date is invalid', new FormattableMarkup('Invalid end minute value %time has been caught.', ['%time' => $time_value]));
1275 $time_value = '12:15:99';
1277 "{$field_name}[0][value][date]" => '2012-12-01',
1278 "{$field_name}[0][value][time]" => '12:00:00',
1279 "{$field_name}[0][end_value][date]" => '2012-12-01',
1280 "{$field_name}[0][end_value][time]" => $time_value,
1282 $this->drupalPostForm(NULL, $edit, t('Save'));
1283 $this->assertText('date is invalid', new FormattableMarkup('Invalid end second value %time has been caught.', ['%time' => $time_value]));
1286 "{$field_name}[0][value][date]" => '2012-12-01',
1287 "{$field_name}[0][value][time]" => '12:00:00',
1288 "{$field_name}[0][end_value][date]" => '2010-12-01',
1289 "{$field_name}[0][end_value][time]" => '12:00:00',
1291 $this->drupalPostForm(NULL, $edit, t('Save'));
1292 $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End date before start date has been caught.');
1295 "{$field_name}[0][value][date]" => '2012-12-01',
1296 "{$field_name}[0][value][time]" => '12:00:00',
1297 "{$field_name}[0][end_value][date]" => '2012-12-01',
1298 "{$field_name}[0][end_value][time]" => '11:00:00',
1300 $this->drupalPostForm(NULL, $edit, t('Save'));
1301 $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_name]), 'End time before start time has been caught.');
1305 * Tests that 'Date' field storage setting form is disabled if field has data.
1307 public function testDateStorageSettings() {
1308 // Create a test content type.
1309 $this->drupalCreateContentType(['type' => 'date_content']);
1311 // Create a field storage with settings to validate.
1312 $field_name = Unicode::strtolower($this->randomMachineName());
1313 $field_storage = FieldStorageConfig::create([
1314 'field_name' => $field_name,
1315 'entity_type' => 'node',
1316 'type' => 'daterange',
1318 'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
1321 $field_storage->save();
1322 $field = FieldConfig::create([
1323 'field_storage' => $field_storage,
1324 'field_name' => $field_name,
1325 'bundle' => 'date_content',
1329 entity_get_form_display('node', 'date_content', 'default')
1330 ->setComponent($field_name, [
1331 'type' => 'datetime_default',
1335 'title[0][value]' => $this->randomString(),
1336 'body[0][value]' => $this->randomString(),
1337 $field_name . '[0][value][date]' => '2016-04-01',
1338 $field_name . '[0][end_value][date]' => '2016-04-02',
1340 $this->drupalPostForm('node/add/date_content', $edit, t('Save'));
1341 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage');
1342 $result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]");
1343 $this->assertEqual(count($result), 1, "Changing datetime setting is disabled.");
1344 $this->assertText('There is data for this field in the database. The field settings can no longer be changed.');