3 namespace Drupal\Tests\datetime_range\Functional;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Core\Datetime\DrupalDateTime;
7 use Drupal\Core\Datetime\Entity\DateFormat;
8 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
9 use Drupal\Tests\datetime\Functional\DateTestBase;
10 use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
11 use Drupal\entity_test\Entity\EntityTest;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\field\Entity\FieldStorageConfig;
14 use Drupal\node\Entity\Node;
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();
49 $field_label = $this->field->label();
51 // Loop through defined timezones to test that date-only fields work at the
53 foreach (static::$timezones as $timezone) {
55 $this->setSiteTimezone($timezone);
56 $this->assertEquals($timezone, $this->config('system.date')->get('timezone.default'), 'Time zone set to ' . $timezone);
58 // Ensure field is set to a date-only field.
59 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
60 $this->fieldStorage->save();
62 // Display creation form.
63 $this->drupalGet('entity_test/add');
64 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
65 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
66 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
67 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
68 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
69 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
70 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
71 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
73 // Build up dates in the UTC timezone.
74 $value = '2012-12-31 00:00:00';
75 $start_date = new DrupalDateTime($value, 'UTC');
76 $end_value = '2013-06-06 00:00:00';
77 $end_date = new DrupalDateTime($end_value, 'UTC');
79 // Submit a valid date and ensure it is accepted.
80 $date_format = DateFormat::load('html_date')->getPattern();
81 $time_format = DateFormat::load('html_time')->getPattern();
84 "{$field_name}[0][value][date]" => $start_date->format($date_format),
85 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
87 $this->drupalPostForm(NULL, $edit, t('Save'));
88 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
90 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
91 $this->assertRaw($start_date->format($date_format));
92 $this->assertNoRaw($start_date->format($time_format));
93 $this->assertRaw($end_date->format($date_format));
94 $this->assertNoRaw($end_date->format($time_format));
96 // Verify the date doesn't change when entity is edited through the form.
97 $entity = EntityTest::load($id);
98 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
99 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
100 $this->drupalGet('entity_test/manage/' . $id . '/edit');
101 $this->drupalPostForm(NULL, [], t('Save'));
102 $this->drupalGet('entity_test/manage/' . $id . '/edit');
103 $this->drupalPostForm(NULL, [], t('Save'));
104 $this->drupalGet('entity_test/manage/' . $id . '/edit');
105 $this->drupalPostForm(NULL, [], t('Save'));
106 $entity = EntityTest::load($id);
107 $this->assertEqual('2012-12-31', $entity->{$field_name}->value);
108 $this->assertEqual('2013-06-06', $entity->{$field_name}->end_value);
110 // Formats that display a time component for date-only fields will display
111 // the default time, so that is applied before calculating the expected
113 $this->massageTestDate($start_date);
114 $this->massageTestDate($end_date);
116 // Reset display options since these get changed below.
117 $this->displayOptions = [
118 'type' => 'daterange_default',
121 'format_type' => 'long',
122 'separator' => 'THESEPARATOR',
123 ] + $this->defaultSettings,
126 // Verify that the default formatter works.
127 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
128 ->setComponent($field_name, $this->displayOptions)
131 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
132 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
133 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
134 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
135 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
136 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
137 $output = $this->renderTestEntity($id);
138 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
140 '%expected' => $start_expected,
141 '%expected_iso' => $start_expected_iso,
142 '%timezone' => $timezone,
144 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
146 '%expected' => $end_expected,
147 '%expected_iso' => $end_expected_iso,
148 '%timezone' => $timezone,
150 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
152 // Verify that hook_entity_prepare_view can add attributes.
153 // @see entity_test_entity_prepare_view()
154 $this->drupalGet('entity_test/' . $id);
155 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
157 // Verify that the plain formatter works.
158 $this->displayOptions['type'] = 'daterange_plain';
159 $this->displayOptions['settings'] = $this->defaultSettings;
160 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
161 ->setComponent($field_name, $this->displayOptions)
163 $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
164 $output = $this->renderTestEntity($id);
165 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
166 '%expected' => $expected,
167 '%timezone' => $timezone,
170 // Verify that the custom formatter works.
171 $this->displayOptions['type'] = 'daterange_custom';
172 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
173 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
174 ->setComponent($field_name, $this->displayOptions)
176 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
177 $output = $this->renderTestEntity($id);
178 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
179 '%expected' => $expected,
180 '%timezone' => $timezone,
183 // Test that allowed markup in custom format is preserved and XSS is
185 $this->displayOptions['settings']['date_format'] = '\\<\\s\\t\\r\\o\\n\\g\\>m/d/Y\\<\\/\\s\\t\\r\\o\\n\\g\\>\\<\\s\\c\\r\\i\\p\\t\\>\\a\\l\\e\\r\\t\\(\\S\\t\\r\\i\\n\\g\\.\\f\\r\\o\\m\\C\\h\\a\\r\\C\\o\\d\\e\\(\\8\\8\\,\\8\\3\\,\\8\\3\\)\\)\\<\\/\\s\\c\\r\\i\\p\\t\\>';
186 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
187 ->setComponent($field_name, $this->displayOptions)
189 $expected = '<strong>' . $start_date->format('m/d/Y') . '</strong>alert(String.fromCharCode(88,83,83)) - <strong>' . $end_date->format('m/d/Y') . '</strong>alert(String.fromCharCode(88,83,83))';
190 $output = $this->renderTestEntity($id);
191 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
192 '%expected' => $expected,
193 '%timezone' => $timezone,
196 // Test formatters when start date and end date are the same
197 $this->drupalGet('entity_test/add');
198 $value = '2012-12-31 00:00:00';
199 $start_date = new DrupalDateTime($value, 'UTC');
201 $date_format = DateFormat::load('html_date')->getPattern();
202 $time_format = DateFormat::load('html_time')->getPattern();
205 "{$field_name}[0][value][date]" => $start_date->format($date_format),
206 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
209 $this->drupalPostForm(NULL, $edit, t('Save'));
210 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
212 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
214 $this->massageTestDate($start_date);
216 $this->displayOptions = [
217 'type' => 'daterange_default',
220 'format_type' => 'long',
221 'separator' => 'THESEPARATOR',
222 ] + $this->defaultSettings,
225 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
226 ->setComponent($field_name, $this->displayOptions)
229 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE);
230 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE);
231 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
232 $output = $this->renderTestEntity($id);
233 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [
235 '%expected' => $start_expected,
236 '%expected_iso' => $start_expected_iso,
237 '%timezone' => $timezone,
239 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page in ' . $timezone);
241 // Verify that hook_entity_prepare_view can add attributes.
242 // @see entity_test_entity_prepare_view()
243 $this->drupalGet('entity_test/' . $id);
244 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
246 $this->displayOptions['type'] = 'daterange_plain';
247 $this->displayOptions['settings'] = $this->defaultSettings;
248 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
249 ->setComponent($field_name, $this->displayOptions)
251 $expected = $start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
252 $output = $this->renderTestEntity($id);
253 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected in %timezone.', [
254 '%expected' => $expected,
255 '%timezone' => $timezone,
257 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
259 $this->displayOptions['type'] = 'daterange_custom';
260 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
261 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
262 ->setComponent($field_name, $this->displayOptions)
264 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
265 $output = $this->renderTestEntity($id);
266 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected in %timezone.', [
267 '%expected' => $expected,
268 '%timezone' => $timezone,
270 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
275 * Tests date and time field.
277 public function testDatetimeRangeField() {
278 $field_name = $this->fieldStorage->getName();
279 $field_label = $this->field->label();
281 // Ensure the field to a datetime field.
282 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
283 $this->fieldStorage->save();
285 // Display creation form.
286 $this->drupalGet('entity_test/add');
287 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
288 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
289 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
290 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
291 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
292 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
293 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
295 // Build up dates in the UTC timezone.
296 $value = '2012-12-31 00:00:00';
297 $start_date = new DrupalDateTime($value, 'UTC');
298 $end_value = '2013-06-06 00:00:00';
299 $end_date = new DrupalDateTime($end_value, 'UTC');
301 // Update the timezone to the system default.
302 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
303 $end_date->setTimezone(timezone_open(drupal_get_user_timezone()));
305 // Submit a valid date and ensure it is accepted.
306 $date_format = DateFormat::load('html_date')->getPattern();
307 $time_format = DateFormat::load('html_time')->getPattern();
310 "{$field_name}[0][value][date]" => $start_date->format($date_format),
311 "{$field_name}[0][value][time]" => $start_date->format($time_format),
312 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
313 "{$field_name}[0][end_value][time]" => $end_date->format($time_format),
315 $this->drupalPostForm(NULL, $edit, t('Save'));
316 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
318 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
319 $this->assertRaw($start_date->format($date_format));
320 $this->assertRaw($start_date->format($time_format));
321 $this->assertRaw($end_date->format($date_format));
322 $this->assertRaw($end_date->format($time_format));
324 // Verify that the default formatter works.
325 $this->displayOptions['settings'] = [
326 'format_type' => 'long',
327 'separator' => 'THESEPARATOR',
328 ] + $this->defaultSettings;
329 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
330 ->setComponent($field_name, $this->displayOptions)
333 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
334 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
335 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
336 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
337 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
338 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
339 $output = $this->renderTestEntity($id);
340 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
341 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
342 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
344 // Verify that hook_entity_prepare_view can add attributes.
345 // @see entity_test_entity_prepare_view()
346 $this->drupalGet('entity_test/' . $id);
347 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
349 // Verify that the plain formatter works.
350 $this->displayOptions['type'] = 'daterange_plain';
351 $this->displayOptions['settings'] = $this->defaultSettings;
352 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
353 ->setComponent($field_name, $this->displayOptions)
355 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
356 $output = $this->renderTestEntity($id);
357 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
359 // Verify that the 'datetime_custom' formatter works.
360 $this->displayOptions['type'] = 'daterange_custom';
361 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
362 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
363 ->setComponent($field_name, $this->displayOptions)
365 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
366 $output = $this->renderTestEntity($id);
367 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
369 // Verify that the 'timezone_override' setting works.
370 $this->displayOptions['type'] = 'daterange_custom';
371 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
372 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
373 ->setComponent($field_name, $this->displayOptions)
375 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
376 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
377 $output = $this->renderTestEntity($id);
378 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
380 // Test formatters when start date and end date are the same
381 $this->drupalGet('entity_test/add');
382 $value = '2012-12-31 00:00:00';
383 $start_date = new DrupalDateTime($value, 'UTC');
384 $start_date->setTimezone(timezone_open(drupal_get_user_timezone()));
386 $date_format = DateFormat::load('html_date')->getPattern();
387 $time_format = DateFormat::load('html_time')->getPattern();
390 "{$field_name}[0][value][date]" => $start_date->format($date_format),
391 "{$field_name}[0][value][time]" => $start_date->format($time_format),
392 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
393 "{$field_name}[0][end_value][time]" => $start_date->format($time_format),
396 $this->drupalPostForm(NULL, $edit, t('Save'));
397 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
399 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
401 $this->displayOptions = [
402 'type' => 'daterange_default',
405 'format_type' => 'long',
406 'separator' => 'THESEPARATOR',
407 ] + $this->defaultSettings,
410 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
411 ->setComponent($field_name, $this->displayOptions)
414 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
415 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
416 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
417 $output = $this->renderTestEntity($id);
418 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
419 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
421 // Verify that hook_entity_prepare_view can add attributes.
422 // @see entity_test_entity_prepare_view()
423 $this->drupalGet('entity_test/' . $id);
424 $this->assertFieldByXPath('//time[@data-field-item-attr="foobar"]');
426 $this->displayOptions['type'] = 'daterange_plain';
427 $this->displayOptions['settings'] = $this->defaultSettings;
428 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
429 ->setComponent($field_name, $this->displayOptions)
431 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
432 $output = $this->renderTestEntity($id);
433 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
434 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
436 $this->displayOptions['type'] = 'daterange_custom';
437 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A'] + $this->defaultSettings;
438 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
439 ->setComponent($field_name, $this->displayOptions)
441 $expected = $start_date->format($this->displayOptions['settings']['date_format']);
442 $output = $this->renderTestEntity($id);
443 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
444 $this->assertNotContains(' THESEPARATOR ', $output, 'Separator not found on page');
448 * Tests all-day field.
450 public function testAlldayRangeField() {
451 $field_name = $this->fieldStorage->getName();
452 $field_label = $this->field->label();
454 // Ensure field is set to a all-day field.
455 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
456 $this->fieldStorage->save();
458 // Display creation form.
459 $this->drupalGet('entity_test/add');
460 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
461 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
462 $this->assertFieldByXPath('//*[@id="edit-' . $field_name . '-wrapper"]//label[contains(@class, "js-form-required")]', TRUE, 'Required markup found');
463 $this->assertNoFieldByName("{$field_name}[0][value][time]", '', 'Start time element not found.');
464 $this->assertNoFieldByName("{$field_name}[0][end_value][time]", '', 'End time element not found.');
465 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
466 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
467 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
469 // Build up dates in the proper timezone.
470 $value = '2012-12-31 00:00:00';
471 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
472 $end_value = '2013-06-06 23:59:59';
473 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
475 // Submit a valid date and ensure it is accepted.
476 $date_format = DateFormat::load('html_date')->getPattern();
477 $time_format = DateFormat::load('html_time')->getPattern();
480 "{$field_name}[0][value][date]" => $start_date->format($date_format),
481 "{$field_name}[0][end_value][date]" => $end_date->format($date_format),
483 $this->drupalPostForm(NULL, $edit, t('Save'));
484 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
486 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
487 $this->assertRaw($start_date->format($date_format));
488 $this->assertNoRaw($start_date->format($time_format));
489 $this->assertRaw($end_date->format($date_format));
490 $this->assertNoRaw($end_date->format($time_format));
492 // Verify that the default formatter works.
493 $this->displayOptions['settings'] = [
494 'format_type' => 'long',
495 'separator' => 'THESEPARATOR',
496 ] + $this->defaultSettings;
497 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
498 ->setComponent($field_name, $this->displayOptions)
501 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
502 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
503 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
504 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
505 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
506 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
507 $output = $this->renderTestEntity($id);
508 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
509 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
510 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
512 // Verify that hook_entity_prepare_view can add attributes.
513 // @see entity_test_entity_prepare_view()
514 $this->drupalGet('entity_test/' . $id);
515 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
517 // Verify that the plain formatter works.
518 $this->displayOptions['type'] = 'daterange_plain';
519 $this->displayOptions['settings'] = $this->defaultSettings;
520 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
521 ->setComponent($field_name, $this->displayOptions)
523 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' - ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
524 $output = $this->renderTestEntity($id);
525 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
527 // Verify that the custom formatter works.
528 $this->displayOptions['type'] = 'daterange_custom';
529 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y'] + $this->defaultSettings;
530 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
531 ->setComponent($field_name, $this->displayOptions)
533 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' - ' . $end_date->format($this->displayOptions['settings']['date_format']);
534 $output = $this->renderTestEntity($id);
535 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
537 // Verify that the 'timezone_override' setting works.
538 $this->displayOptions['type'] = 'daterange_custom';
539 $this->displayOptions['settings'] = ['date_format' => 'm/d/Y g:i:s A', 'timezone_override' => 'America/New_York'] + $this->defaultSettings;
540 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
541 ->setComponent($field_name, $this->displayOptions)
543 $expected = $start_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
544 $expected .= ' - ' . $end_date->format($this->displayOptions['settings']['date_format'], ['timezone' => 'America/New_York']);
545 $output = $this->renderTestEntity($id);
546 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
548 // Test formatters when start date and end date are the same
549 $this->drupalGet('entity_test/add');
551 $value = '2012-12-31 00:00:00';
552 $start_date = new DrupalDateTime($value, timezone_open(drupal_get_user_timezone()));
553 $end_value = '2012-12-31 23:59:59';
554 $end_date = new DrupalDateTime($end_value, timezone_open(drupal_get_user_timezone()));
556 $date_format = DateFormat::load('html_date')->getPattern();
557 $time_format = DateFormat::load('html_time')->getPattern();
560 "{$field_name}[0][value][date]" => $start_date->format($date_format),
561 "{$field_name}[0][end_value][date]" => $start_date->format($date_format),
563 $this->drupalPostForm(NULL, $edit, t('Save'));
564 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
566 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
568 $this->displayOptions = [
569 'type' => 'daterange_default',
572 'format_type' => 'long',
573 'separator' => 'THESEPARATOR',
574 ] + $this->defaultSettings,
577 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
578 ->setComponent($field_name, $this->displayOptions)
581 $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long');
582 $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
583 $start_expected_markup = '<time datetime="' . $start_expected_iso . '" class="datetime">' . $start_expected . '</time>';
584 $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long');
585 $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC');
586 $end_expected_markup = '<time datetime="' . $end_expected_iso . '" class="datetime">' . $end_expected . '</time>';
587 $output = $this->renderTestEntity($id);
588 $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso]));
589 $this->assertContains($end_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $end_expected, '%expected_iso' => $end_expected_iso]));
590 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
592 // Verify that hook_entity_prepare_view can add attributes.
593 // @see entity_test_entity_prepare_view()
594 $this->drupalGet('entity_test/' . $id);
595 $this->assertFieldByXPath('//div[@data-field-item-attr="foobar"]');
597 $this->displayOptions['type'] = 'daterange_plain';
598 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
599 ->setComponent($field_name, $this->displayOptions)
601 $expected = $start_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT) . ' THESEPARATOR ' . $end_date->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
602 $output = $this->renderTestEntity($id);
603 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using plain format displayed as %expected.', ['%expected' => $expected]));
604 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
606 $this->displayOptions['type'] = 'daterange_custom';
607 $this->displayOptions['settings']['date_format'] = 'm/d/Y';
608 entity_get_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'full')
609 ->setComponent($field_name, $this->displayOptions)
611 $expected = $start_date->format($this->displayOptions['settings']['date_format']) . ' THESEPARATOR ' . $end_date->format($this->displayOptions['settings']['date_format']);
612 $output = $this->renderTestEntity($id);
613 $this->assertContains($expected, $output, new FormattableMarkup('Formatted date field using daterange_custom format displayed as %expected.', ['%expected' => $expected]));
614 $this->assertContains(' THESEPARATOR ', $output, 'Found proper separator');
619 * Tests Date Range List Widget functionality.
621 public function testDatelistWidget() {
622 $field_name = $this->fieldStorage->getName();
623 $field_label = $this->field->label();
625 // Ensure field is set to a date only field.
626 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATE);
627 $this->fieldStorage->save();
629 // Change the widget to a datelist widget.
630 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
631 ->setComponent($field_name, [
632 'type' => 'daterange_datelist',
634 'date_order' => 'YMD',
638 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
640 // Display creation form.
641 $this->drupalGet('entity_test/add');
642 $this->assertFieldByXPath('//fieldset[@id="edit-' . $field_name . '-0"]/legend', $field_label, 'Fieldset and label found');
643 $this->assertFieldByXPath('//fieldset[@aria-describedby="edit-' . $field_name . '-0--description"]', NULL, 'ARIA described-by found');
644 $this->assertFieldByXPath('//div[@id="edit-' . $field_name . '-0--description"]', NULL, 'ARIA description found');
646 // Assert that Hour and Minute Elements do not appear on Date Only.
647 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
648 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
649 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
650 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
652 // Go to the form display page to assert that increment option does not
653 // appear on Date Only.
654 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
655 $this->drupalGet($fieldEditUrl);
657 // Click on the widget settings button to open the widget settings form.
658 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
659 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
660 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
662 // Change the field is set to an all day field.
663 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_ALLDAY);
664 $this->fieldStorage->save();
666 // Change the widget to a datelist widget.
667 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
668 ->setComponent($field_name, [
669 'type' => 'daterange_datelist',
671 'date_order' => 'YMD',
675 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
677 // Display creation form.
678 $this->drupalGet('entity_test/add');
680 // Assert that Hour and Minute Elements do not appear on Date Only.
681 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element not found on Date Only.');
682 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-minute\"]", NULL, 'Minute element not found on Date Only.');
683 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element not found on Date Only.');
684 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-minute\"]", NULL, 'Minute element not found on Date Only.');
686 // Go to the form display page to assert that increment option does not
687 // appear on Date Only.
688 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
689 $this->drupalGet($fieldEditUrl);
691 // Click on the widget settings button to open the widget settings form.
692 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
693 $xpathIncr = "//select[starts-with(@id, \"edit-fields-$field_name-settings-edit-form-settings-increment\")]";
694 $this->assertNoFieldByXPath($xpathIncr, NULL, 'Increment element not found for Date Only.');
696 // Change the field to a datetime field.
697 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
698 $this->fieldStorage->save();
700 // Change the widget to a datelist widget.
701 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
702 ->setComponent($field_name, [
703 'type' => 'daterange_datelist',
706 'date_order' => 'YMD',
711 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
713 // Go to the form display page to assert that increment option does appear
715 $fieldEditUrl = 'entity_test/structure/entity_test/form-display';
716 $this->drupalGet($fieldEditUrl);
718 // Click on the widget settings button to open the widget settings form.
719 $this->drupalPostForm(NULL, [], $field_name . "_settings_edit");
720 $this->assertFieldByXPath($xpathIncr, NULL, 'Increment element found for Date and time.');
722 // Display creation form.
723 $this->drupalGet('entity_test/add');
725 foreach (['value', 'end-value'] as $column) {
726 foreach (['year', 'month', 'day', 'hour', 'minute', 'ampm'] as $element) {
727 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-$column-$element\"]", NULL, $element . ' element found.');
728 $this->assertOptionSelected("edit-$field_name-0-$column-$element", '', 'No ' . $element . ' selected.');
732 // Submit a valid date and ensure it is accepted.
733 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 5, 'minute' => 15];
734 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
737 // Add the ampm indicator since we are testing 12 hour time.
738 $start_date_value['ampm'] = 'am';
739 $end_date_value['ampm'] = 'pm';
740 foreach ($start_date_value as $part => $value) {
741 $edit["{$field_name}[0][value][$part]"] = $value;
743 foreach ($end_date_value as $part => $value) {
744 $edit["{$field_name}[0][end_value][$part]"] = $value;
747 $this->drupalPostForm(NULL, $edit, t('Save'));
748 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
750 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
752 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
753 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
754 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
755 $this->assertOptionSelected("edit-$field_name-0-value-hour", '5', 'Correct hour selected.');
756 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
757 $this->assertOptionSelected("edit-$field_name-0-value-ampm", 'am', 'Correct ampm selected.');
759 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
760 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
761 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
762 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
763 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
764 $this->assertOptionSelected("edit-$field_name-0-end-value-ampm", 'pm', 'Correct ampm selected.');
766 // Test the widget using increment other than 1 and 24 hour mode.
767 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
768 ->setComponent($field_name, [
769 'type' => 'daterange_datelist',
772 'date_order' => 'YMD',
777 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
779 // Display creation form.
780 $this->drupalGet('entity_test/add');
782 // Other elements are unaffected by the changed settings.
783 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-value-hour\"]", NULL, 'Hour element found.');
784 $this->assertOptionSelected("edit-$field_name-0-value-hour", '', 'No hour selected.');
785 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-value-ampm\"]", NULL, 'AMPM element not found.');
786 $this->assertFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-hour\"]", NULL, 'Hour element found.');
787 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '', 'No hour selected.');
788 $this->assertNoFieldByXPath("//*[@id=\"edit-$field_name-0-end-value-ampm\"]", NULL, 'AMPM element not found.');
790 // Submit a valid date and ensure it is accepted.
791 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 17, 'minute' => 15];
792 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
795 foreach ($start_date_value as $part => $value) {
796 $edit["{$field_name}[0][value][$part]"] = $value;
798 foreach ($end_date_value as $part => $value) {
799 $edit["{$field_name}[0][end_value][$part]"] = $value;
802 $this->drupalPostForm(NULL, $edit, t('Save'));
803 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
805 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
807 $this->assertOptionSelected("edit-$field_name-0-value-year", '2012', 'Correct year selected.');
808 $this->assertOptionSelected("edit-$field_name-0-value-month", '12', 'Correct month selected.');
809 $this->assertOptionSelected("edit-$field_name-0-value-day", '31', 'Correct day selected.');
810 $this->assertOptionSelected("edit-$field_name-0-value-hour", '17', 'Correct hour selected.');
811 $this->assertOptionSelected("edit-$field_name-0-value-minute", '15', 'Correct minute selected.');
813 $this->assertOptionSelected("edit-$field_name-0-end-value-year", '2013', 'Correct year selected.');
814 $this->assertOptionSelected("edit-$field_name-0-end-value-month", '1', 'Correct month selected.');
815 $this->assertOptionSelected("edit-$field_name-0-end-value-day", '15', 'Correct day selected.');
816 $this->assertOptionSelected("edit-$field_name-0-end-value-hour", '3', 'Correct hour selected.');
817 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '30', 'Correct minute selected.');
819 // Test the widget for partial completion of fields.
820 entity_get_form_display($this->field->getTargetEntityTypeId(), $this->field->getTargetBundle(), 'default')
821 ->setComponent($field_name, [
822 'type' => 'daterange_datelist',
825 'date_order' => 'YMD',
830 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
832 // Test the widget for validation notifications.
833 foreach ($this->datelistDataProvider() as $data) {
834 list($start_date_value, $end_date_value, $expected) = $data;
836 // Display creation form.
837 $this->drupalGet('entity_test/add');
839 // Submit a partial date and ensure and error message is provided.
841 foreach ($start_date_value as $part => $value) {
842 $edit["{$field_name}[0][value][$part]"] = $value;
844 foreach ($end_date_value as $part => $value) {
845 $edit["{$field_name}[0][end_value][$part]"] = $value;
848 $this->drupalPostForm(NULL, $edit, t('Save'));
849 $this->assertResponse(200);
850 foreach ($expected as $expected_text) {
851 $this->assertText(t($expected_text));
855 // Test the widget for complete input with zeros as part of selections.
856 $this->drupalGet('entity_test/add');
858 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
859 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 30];
861 foreach ($start_date_value as $part => $value) {
862 $edit["{$field_name}[0][value][$part]"] = $value;
864 foreach ($end_date_value as $part => $value) {
865 $edit["{$field_name}[0][end_value][$part]"] = $value;
868 $this->drupalPostForm(NULL, $edit, t('Save'));
869 $this->assertResponse(200);
870 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
872 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
874 // Test the widget to ensure zeros are not deselected on validation.
875 $this->drupalGet('entity_test/add');
877 $start_date_value = ['year' => 2012, 'month' => 12, 'day' => 31, 'hour' => 0, 'minute' => 0];
878 $end_date_value = ['year' => 2013, 'month' => 1, 'day' => 15, 'hour' => 3, 'minute' => 0];
880 foreach ($start_date_value as $part => $value) {
881 $edit["{$field_name}[0][value][$part]"] = $value;
883 foreach ($end_date_value as $part => $value) {
884 $edit["{$field_name}[0][end_value][$part]"] = $value;
887 $this->drupalPostForm(NULL, $edit, t('Save'));
888 $this->assertResponse(200);
889 $this->assertOptionSelected("edit-$field_name-0-value-minute", '0', 'Correct minute selected.');
890 $this->assertOptionSelected("edit-$field_name-0-end-value-minute", '0', 'Correct minute selected.');
894 * The data provider for testing the validation of the datelist widget.
897 * An array of datelist input permutations to test.
899 protected function datelistDataProvider() {
901 // Year only selected, validation error on Month, Day, Hour, Minute.
903 ['year' => 2012, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''],
904 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
905 'A value must be selected for month.',
906 'A value must be selected for day.',
907 'A value must be selected for hour.',
908 'A value must be selected for minute.',
911 // Year and Month selected, validation error on Day, Hour, Minute.
913 ['year' => 2012, 'month' => '12', 'day' => '', 'hour' => '', 'minute' => ''],
914 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
915 'A value must be selected for day.',
916 'A value must be selected for hour.',
917 'A value must be selected for minute.',
920 // Year, Month and Day selected, validation error on Hour, Minute.
922 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '', 'minute' => ''],
923 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
924 'A value must be selected for hour.',
925 'A value must be selected for minute.',
928 // Year, Month, Day and Hour selected, validation error on Minute only.
930 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => ''],
931 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => '30'], [
932 'A value must be selected for minute.',
935 // Year selected, validation error on Month, Day, Hour, Minute.
937 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
938 ['year' => 2013, 'month' => '', 'day' => '', 'hour' => '', 'minute' => ''], [
939 'A value must be selected for month.',
940 'A value must be selected for day.',
941 'A value must be selected for hour.',
942 'A value must be selected for minute.',
945 // Year and Month selected, validation error on Day, Hour, Minute.
947 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
948 ['year' => 2013, 'month' => '1', 'day' => '', 'hour' => '', 'minute' => ''], [
949 'A value must be selected for day.',
950 'A value must be selected for hour.',
951 'A value must be selected for minute.',
954 // Year, Month and Day selected, validation error on Hour, Minute.
956 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
957 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '', 'minute' => ''], [
958 'A value must be selected for hour.',
959 'A value must be selected for minute.',
962 // Year, Month, Day and Hour selected, validation error on Minute only.
964 ['year' => 2012, 'month' => '12', 'day' => '31', 'hour' => '0', 'minute' => '0'],
965 ['year' => 2013, 'month' => '1', 'day' => '15', 'hour' => '3', 'minute' => ''], [
966 'A value must be selected for minute.',
973 * Test default value functionality.
975 public function testDefaultValue() {
976 // Create a test content type.
977 $this->drupalCreateContentType(['type' => 'date_content']);
979 // Create a field storage with settings to validate.
980 $field_name = mb_strtolower($this->randomMachineName());
981 $field_storage = FieldStorageConfig::create([
982 'field_name' => $field_name,
983 'entity_type' => 'node',
984 'type' => 'daterange',
985 'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_DATE],
987 $field_storage->save();
989 $field = FieldConfig::create([
990 'field_storage' => $field_storage,
991 'bundle' => 'date_content',
995 // Set now as default_value.
997 'default_value_input[default_date_type]' => 'now',
998 'default_value_input[default_end_date_type]' => 'now',
1000 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1002 // Check that default value is selected in default value form.
1003 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1004 $this->assertOptionSelected('edit-default-value-input-default-date-type', 'now', 'The default start value is selected in instance settings page');
1005 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative start default value is empty in instance settings page');
1006 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'now', 'The default end value is selected in instance settings page');
1007 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative end default value is empty in instance settings page');
1009 // Check if default_date has been stored successfully.
1010 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1011 $this->assertEqual($config_entity['default_value'][0], [
1012 'default_date_type' => 'now',
1013 'default_date' => 'now',
1014 'default_end_date_type' => 'now',
1015 'default_end_date' => 'now',
1016 ], 'Default value has been stored successfully');
1018 // Clear field cache in order to avoid stale cache values.
1019 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1021 // Create a new node to check that datetime field default value is today.
1022 $new_node = Node::create(['type' => 'date_content']);
1023 $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1024 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1025 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1027 // Set an invalid relative default_value to test validation.
1029 'default_value_input[default_date_type]' => 'relative',
1030 'default_value_input[default_date]' => 'invalid date',
1031 'default_value_input[default_end_date_type]' => 'relative',
1032 'default_value_input[default_end_date]' => '+1 day',
1034 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1035 $this->assertText('The relative start date value entered is invalid.');
1038 'default_value_input[default_date_type]' => 'relative',
1039 'default_value_input[default_date]' => '+1 day',
1040 'default_value_input[default_end_date_type]' => 'relative',
1041 'default_value_input[default_end_date]' => 'invalid date',
1043 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1044 $this->assertText('The relative end date value entered is invalid.');
1046 // Set a relative default_value.
1048 'default_value_input[default_date_type]' => 'relative',
1049 'default_value_input[default_date]' => '+45 days',
1050 'default_value_input[default_end_date_type]' => 'relative',
1051 'default_value_input[default_end_date]' => '+90 days',
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', 'relative', 'The default start value is selected in instance settings page');
1058 $this->assertFieldByName('default_value_input[default_date]', '+45 days', 'The relative default start value is displayed in instance settings page');
1059 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', 'relative', 'The default end value is selected in instance settings page');
1060 $this->assertFieldByName('default_value_input[default_end_date]', '+90 days', 'The relative default end value is displayed in instance settings page');
1062 // Check if default_date has been stored successfully.
1063 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1064 $this->assertEqual($config_entity['default_value'][0], [
1065 'default_date_type' => 'relative',
1066 'default_date' => '+45 days',
1067 'default_end_date_type' => 'relative',
1068 'default_end_date' => '+90 days',
1069 ], 'Default value has been stored successfully');
1071 // Clear field cache in order to avoid stale cache values.
1072 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1074 // Create a new node to check that datetime field default value is +90 days.
1075 $new_node = Node::create(['type' => 'date_content']);
1076 $expected_start_date = new DrupalDateTime('+45 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1077 $expected_end_date = new DrupalDateTime('+90 days', DateTimeItemInterface::STORAGE_TIMEZONE);
1078 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->value, $expected_start_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1079 $this->assertEqual($new_node->get($field_name)->offsetGet(0)->end_value, $expected_end_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT));
1081 // Remove default value.
1083 'default_value_input[default_date_type]' => '',
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 // Check that default value is selected in default value form.
1089 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name);
1090 $this->assertOptionSelected('edit-default-value-input-default-date-type', '', 'The default start value is selected in instance settings page');
1091 $this->assertFieldByName('default_value_input[default_date]', '', 'The relative default start value is empty in instance settings page');
1092 $this->assertOptionSelected('edit-default-value-input-default-end-date-type', '', 'The default end value is selected in instance settings page');
1093 $this->assertFieldByName('default_value_input[default_end_date]', '', 'The relative default end value is empty in instance settings page');
1095 // Check if default_date has been stored successfully.
1096 $config_entity = $this->config('field.field.node.date_content.' . $field_name)->get();
1097 $this->assertTrue(empty($config_entity['default_value']), 'Empty default value has been stored successfully');
1099 // Clear field cache in order to avoid stale cache values.
1100 \Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
1102 // Create a new node to check that datetime field default value is not set.
1103 $new_node = Node::create(['type' => 'date_content']);
1104 $this->assertNull($new_node->get($field_name)->value, 'Default value is not set');
1106 // Set now as default_value for start date only.
1107 entity_get_form_display('node', 'date_content', 'default')
1108 ->setComponent($field_name, [
1109 'type' => 'datetime_default',
1113 $expected_date = new DrupalDateTime('now', DateTimeItemInterface::STORAGE_TIMEZONE);
1116 'default_value_input[default_date_type]' => 'now',
1117 'default_value_input[default_end_date_type]' => '',
1119 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1121 // Make sure only the start value is populated on node add page.
1122 $this->drupalGet('node/add/date_content');
1123 $this->assertFieldByName("{$field_name}[0][value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'Start date element populated.');
1124 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element empty.');
1126 // Set now as default_value for end date only.
1128 'default_value_input[default_date_type]' => '',
1129 'default_value_input[default_end_date_type]' => 'now',
1131 $this->drupalPostForm('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name, $field_edit, t('Save settings'));
1133 // Make sure only the start value is populated on node add page.
1134 $this->drupalGet('node/add/date_content');
1135 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element empty.');
1136 $this->assertFieldByName("{$field_name}[0][end_value][date]", $expected_date->format(DateTimeItemInterface::DATE_STORAGE_FORMAT), 'End date element populated.');
1140 * Test that invalid values are caught and marked as invalid.
1142 public function testInvalidField() {
1143 // Change the field to a datetime field.
1144 $this->fieldStorage->setSetting('datetime_type', DateRangeItem::DATETIME_TYPE_DATETIME);
1145 $this->fieldStorage->save();
1146 $field_name = $this->fieldStorage->getName();
1147 $field_label = $this->field->label();
1149 $this->drupalGet('entity_test/add');
1150 $this->assertFieldByName("{$field_name}[0][value][date]", '', 'Start date element found.');
1151 $this->assertFieldByName("{$field_name}[0][value][time]", '', 'Start time element found.');
1152 $this->assertFieldByName("{$field_name}[0][end_value][date]", '', 'End date element found.');
1153 $this->assertFieldByName("{$field_name}[0][end_value][time]", '', 'End time element found.');
1155 // Submit invalid start dates and ensure they is not accepted.
1158 "{$field_name}[0][value][date]" => $date_value,
1159 "{$field_name}[0][value][time]" => '12:00:00',
1160 "{$field_name}[0][end_value][date]" => '2012-12-01',
1161 "{$field_name}[0][end_value][time]" => '12:00:00',
1163 $this->drupalPostForm(NULL, $edit, t('Save'));
1164 $this->assertText('date is invalid', 'Empty start date value has been caught.');
1166 $date_value = 'aaaa-12-01';
1168 "{$field_name}[0][value][date]" => $date_value,
1169 "{$field_name}[0][value][time]" => '00:00:00',
1170 "{$field_name}[0][end_value][date]" => '2012-12-01',
1171 "{$field_name}[0][end_value][time]" => '12:00:00',
1173 $this->drupalPostForm(NULL, $edit, t('Save'));
1174 $this->assertText('date is invalid', new FormattableMarkup('Invalid start year value %date has been caught.', ['%date' => $date_value]));
1176 $date_value = '2012-75-01';
1178 "{$field_name}[0][value][date]" => $date_value,
1179 "{$field_name}[0][value][time]" => '00:00:00',
1180 "{$field_name}[0][end_value][date]" => '2012-12-01',
1181 "{$field_name}[0][end_value][time]" => '12:00:00',
1183 $this->drupalPostForm(NULL, $edit, t('Save'));
1184 $this->assertText('date is invalid', new FormattableMarkup('Invalid start month value %date has been caught.', ['%date' => $date_value]));
1186 $date_value = '2012-12-99';
1188 "{$field_name}[0][value][date]" => $date_value,
1189 "{$field_name}[0][value][time]" => '00:00:00',
1190 "{$field_name}[0][end_value][date]" => '2012-12-01',
1191 "{$field_name}[0][end_value][time]" => '12:00:00',
1193 $this->drupalPostForm(NULL, $edit, t('Save'));
1194 $this->assertText('date is invalid', new FormattableMarkup('Invalid start day value %date has been caught.', ['%date' => $date_value]));
1196 // Submit invalid start times and ensure they is not accepted.
1199 "{$field_name}[0][value][date]" => '2012-12-01',
1200 "{$field_name}[0][value][time]" => $time_value,
1201 "{$field_name}[0][end_value][date]" => '2012-12-01',
1202 "{$field_name}[0][end_value][time]" => '12:00:00',
1204 $this->drupalPostForm(NULL, $edit, t('Save'));
1205 $this->assertText('date is invalid', 'Empty start time value has been caught.');
1207 $time_value = '49:00:00';
1209 "{$field_name}[0][value][date]" => '2012-12-01',
1210 "{$field_name}[0][value][time]" => $time_value,
1211 "{$field_name}[0][end_value][date]" => '2012-12-01',
1212 "{$field_name}[0][end_value][time]" => '12:00:00',
1214 $this->drupalPostForm(NULL, $edit, t('Save'));
1215 $this->assertText('date is invalid', new FormattableMarkup('Invalid start hour value %time has been caught.', ['%time' => $time_value]));
1217 $time_value = '12:99:00';
1219 "{$field_name}[0][value][date]" => '2012-12-01',
1220 "{$field_name}[0][value][time]" => $time_value,
1221 "{$field_name}[0][end_value][date]" => '2012-12-01',
1222 "{$field_name}[0][end_value][time]" => '12:00:00',
1224 $this->drupalPostForm(NULL, $edit, t('Save'));
1225 $this->assertText('date is invalid', new FormattableMarkup('Invalid start minute value %time has been caught.', ['%time' => $time_value]));
1227 $time_value = '12:15:99';
1229 "{$field_name}[0][value][date]" => '2012-12-01',
1230 "{$field_name}[0][value][time]" => $time_value,
1231 "{$field_name}[0][end_value][date]" => '2012-12-01',
1232 "{$field_name}[0][end_value][time]" => '12:00:00',
1234 $this->drupalPostForm(NULL, $edit, t('Save'));
1235 $this->assertText('date is invalid', new FormattableMarkup('Invalid start second value %time has been caught.', ['%time' => $time_value]));
1237 // Submit invalid end dates and ensure they is not accepted.
1240 "{$field_name}[0][value][date]" => '2012-12-01',
1241 "{$field_name}[0][value][time]" => '12:00:00',
1242 "{$field_name}[0][end_value][date]" => $date_value,
1243 "{$field_name}[0][end_value][time]" => '12:00:00',
1245 $this->drupalPostForm(NULL, $edit, t('Save'));
1246 $this->assertText('date is invalid', 'Empty end date value has been caught.');
1248 $date_value = 'aaaa-12-01';
1250 "{$field_name}[0][value][date]" => '2012-12-01',
1251 "{$field_name}[0][value][time]" => '12:00:00',
1252 "{$field_name}[0][end_value][date]" => $date_value,
1253 "{$field_name}[0][end_value][time]" => '00:00:00',
1255 $this->drupalPostForm(NULL, $edit, t('Save'));
1256 $this->assertText('date is invalid', new FormattableMarkup('Invalid end year value %date has been caught.', ['%date' => $date_value]));
1258 $date_value = '2012-75-01';
1260 "{$field_name}[0][value][date]" => '2012-12-01',
1261 "{$field_name}[0][value][time]" => '12:00:00',
1262 "{$field_name}[0][end_value][date]" => $date_value,
1263 "{$field_name}[0][end_value][time]" => '00:00:00',
1265 $this->drupalPostForm(NULL, $edit, t('Save'));
1266 $this->assertText('date is invalid', new FormattableMarkup('Invalid end month value %date has been caught.', ['%date' => $date_value]));
1268 $date_value = '2012-12-99';
1270 "{$field_name}[0][value][date]" => '2012-12-01',
1271 "{$field_name}[0][value][time]" => '12:00:00',
1272 "{$field_name}[0][end_value][date]" => $date_value,
1273 "{$field_name}[0][end_value][time]" => '00:00:00',
1275 $this->drupalPostForm(NULL, $edit, t('Save'));
1276 $this->assertText('date is invalid', new FormattableMarkup('Invalid end day value %date has been caught.', ['%date' => $date_value]));
1278 // Submit invalid start times and ensure they is not accepted.
1281 "{$field_name}[0][value][date]" => '2012-12-01',
1282 "{$field_name}[0][value][time]" => '12:00:00',
1283 "{$field_name}[0][end_value][date]" => '2012-12-01',
1284 "{$field_name}[0][end_value][time]" => $time_value,
1286 $this->drupalPostForm(NULL, $edit, t('Save'));
1287 $this->assertText('date is invalid', 'Empty end time value has been caught.');
1289 $time_value = '49:00:00';
1291 "{$field_name}[0][value][date]" => '2012-12-01',
1292 "{$field_name}[0][value][time]" => '12:00:00',
1293 "{$field_name}[0][end_value][date]" => '2012-12-01',
1294 "{$field_name}[0][end_value][time]" => $time_value,
1296 $this->drupalPostForm(NULL, $edit, t('Save'));
1297 $this->assertText('date is invalid', new FormattableMarkup('Invalid end hour value %time has been caught.', ['%time' => $time_value]));
1299 $time_value = '12:99:00';
1301 "{$field_name}[0][value][date]" => '2012-12-01',
1302 "{$field_name}[0][value][time]" => '12:00:00',
1303 "{$field_name}[0][end_value][date]" => '2012-12-01',
1304 "{$field_name}[0][end_value][time]" => $time_value,
1306 $this->drupalPostForm(NULL, $edit, t('Save'));
1307 $this->assertText('date is invalid', new FormattableMarkup('Invalid end minute value %time has been caught.', ['%time' => $time_value]));
1309 $time_value = '12:15:99';
1311 "{$field_name}[0][value][date]" => '2012-12-01',
1312 "{$field_name}[0][value][time]" => '12:00:00',
1313 "{$field_name}[0][end_value][date]" => '2012-12-01',
1314 "{$field_name}[0][end_value][time]" => $time_value,
1316 $this->drupalPostForm(NULL, $edit, t('Save'));
1317 $this->assertText('date is invalid', new FormattableMarkup('Invalid end second value %time has been caught.', ['%time' => $time_value]));
1320 "{$field_name}[0][value][date]" => '2012-12-01',
1321 "{$field_name}[0][value][time]" => '12:00:00',
1322 "{$field_name}[0][end_value][date]" => '2010-12-01',
1323 "{$field_name}[0][end_value][time]" => '12:00:00',
1325 $this->drupalPostForm(NULL, $edit, t('Save'));
1326 $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_label]), 'End date before start date has been caught.');
1329 "{$field_name}[0][value][date]" => '2012-12-01',
1330 "{$field_name}[0][value][time]" => '12:00:00',
1331 "{$field_name}[0][end_value][date]" => '2012-12-01',
1332 "{$field_name}[0][end_value][time]" => '11:00:00',
1334 $this->drupalPostForm(NULL, $edit, t('Save'));
1335 $this->assertText(new FormattableMarkup('The @title end date cannot be before the start date', ['@title' => $field_label]), 'End time before start time has been caught.');
1339 * Tests that 'Date' field storage setting form is disabled if field has data.
1341 public function testDateStorageSettings() {
1342 // Create a test content type.
1343 $this->drupalCreateContentType(['type' => 'date_content']);
1345 // Create a field storage with settings to validate.
1346 $field_name = mb_strtolower($this->randomMachineName());
1347 $field_storage = FieldStorageConfig::create([
1348 'field_name' => $field_name,
1349 'entity_type' => 'node',
1350 'type' => 'daterange',
1352 'datetime_type' => DateRangeItem::DATETIME_TYPE_DATE,
1355 $field_storage->save();
1356 $field = FieldConfig::create([
1357 'field_storage' => $field_storage,
1358 'field_name' => $field_name,
1359 'bundle' => 'date_content',
1363 entity_get_form_display('node', 'date_content', 'default')
1364 ->setComponent($field_name, [
1365 'type' => 'datetime_default',
1369 'title[0][value]' => $this->randomString(),
1370 'body[0][value]' => $this->randomString(),
1371 $field_name . '[0][value][date]' => '2016-04-01',
1372 $field_name . '[0][end_value][date]' => '2016-04-02',
1374 $this->drupalPostForm('node/add/date_content', $edit, t('Save'));
1375 $this->drupalGet('admin/structure/types/manage/date_content/fields/node.date_content.' . $field_name . '/storage');
1376 $result = $this->xpath("//*[@id='edit-settings-datetime-type' and contains(@disabled, 'disabled')]");
1377 $this->assertEqual(count($result), 1, "Changing datetime setting is disabled.");
1378 $this->assertText('There is data for this field in the database. The field settings can no longer be changed.');