3 namespace Drupal\Tests\field_ui\Functional;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Core\Field\FieldStorageDefinitionInterface;
7 use Drupal\Core\Language\LanguageInterface;
8 use Drupal\field\Entity\FieldConfig;
9 use Drupal\field\Entity\FieldStorageConfig;
10 use Drupal\taxonomy\Entity\Vocabulary;
11 use Drupal\Tests\BrowserTestBase;
12 use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
13 use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
16 * Tests the Field UI "Manage fields" screen.
20 class ManageFieldsFunctionalTest extends BrowserTestBase {
23 use EntityReferenceTestTrait;
30 public static $modules = ['node', 'field_ui', 'field_test', 'taxonomy', 'image', 'block'];
33 * The ID of the custom content type created for testing.
37 protected $contentType;
40 * The label for a random field to be created for testing.
44 protected $fieldLabel;
47 * The input name of a random field to be created for testing.
51 protected $fieldNameInput;
54 * The name of a random field to be created for testing.
63 protected function setUp() {
66 $this->drupalPlaceBlock('system_breadcrumb_block');
67 $this->drupalPlaceBlock('local_actions_block');
68 $this->drupalPlaceBlock('local_tasks_block');
69 $this->drupalPlaceBlock('page_title_block');
71 // Create a test user.
72 $admin_user = $this->drupalCreateUser(['access content', 'administer content types', 'administer node fields', 'administer node form display', 'administer node display', 'administer taxonomy', 'administer taxonomy_term fields', 'administer taxonomy_term display', 'administer users', 'administer account settings', 'administer user display', 'bypass node access']);
73 $this->drupalLogin($admin_user);
75 // Create content type, with underscores.
76 $type_name = strtolower($this->randomMachineName(8)) . '_test';
77 $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
78 $this->contentType = $type->id();
80 // Create random field name with markup to test escaping.
81 $this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
82 $this->fieldNameInput = strtolower($this->randomMachineName(8));
83 $this->fieldName = 'field_' . $this->fieldNameInput;
85 // Create Basic page and Article node types.
86 $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
87 $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
89 // Create a vocabulary named "Tags".
90 $vocabulary = Vocabulary::create([
93 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
99 $vocabulary->id() => $vocabulary->id(),
102 $this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
104 entity_get_form_display('node', 'article', 'default')
105 ->setComponent('field_' . $vocabulary->id())
110 * Runs the field CRUD tests.
112 * In order to act on the same fields, and not create the fields over and over
113 * again the following tests create, update and delete the same fields.
115 public function testCRUDFields() {
116 $this->manageFieldsPage();
117 $this->createField();
118 $this->updateField();
119 $this->addExistingField();
120 $this->cardinalitySettings();
121 $this->fieldListAdminPage();
122 $this->deleteField();
123 $this->addPersistentFieldStorage();
127 * Tests the manage fields page.
129 * @param string $type
130 * (optional) The name of a content type.
132 public function manageFieldsPage($type = '') {
133 $type = empty($type) ? $this->contentType : $type;
134 $this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
135 // Check all table columns.
142 foreach ($table_headers as $table_header) {
143 // We check that the label appear in the table headings.
144 $this->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', ['%table_header' => $table_header]));
147 // Test the "Add field" action link.
148 $this->assertLink('Add field');
150 // Assert entity operations for all fields.
151 $number_of_links = 3;
152 $number_of_links_found = 0;
153 $operation_links = $this->xpath('//ul[@class = "dropbutton"]/li/a');
154 $url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body";
156 foreach ($operation_links as $link) {
157 switch ($link->getAttribute('title')) {
158 case 'Edit field settings.':
159 $this->assertIdentical($url, $link->getAttribute('href'));
160 $number_of_links_found++;
162 case 'Edit storage settings.':
163 $this->assertIdentical("$url/storage", $link->getAttribute('href'));
164 $number_of_links_found++;
166 case 'Delete field.':
167 $this->assertIdentical("$url/delete", $link->getAttribute('href'));
168 $number_of_links_found++;
173 $this->assertEqual($number_of_links, $number_of_links_found);
177 * Tests adding a new field.
179 * @todo Assert properties can bet set in the form and read back in
180 * $field_storage and $fields.
182 public function createField() {
183 // Create a test field.
184 $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
188 * Tests editing an existing field.
190 public function updateField() {
191 $field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
192 // Go to the field edit page.
193 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
194 $this->assertEscaped($this->fieldLabel);
196 // Populate the field settings with new settings.
197 $string = 'updated dummy test string';
199 'settings[test_field_storage_setting]' => $string,
201 $this->drupalPostForm(NULL, $edit, t('Save field settings'));
203 // Go to the field edit page.
204 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
206 'settings[test_field_setting]' => $string,
208 $this->assertText(t('Default value'), 'Default value heading is shown');
209 $this->drupalPostForm(NULL, $edit, t('Save settings'));
211 // Assert the field settings are correct.
212 $this->assertFieldSettings($this->contentType, $this->fieldName, $string);
214 // Assert redirection back to the "manage fields" page.
215 $this->assertUrl('admin/structure/types/manage/' . $this->contentType . '/fields');
219 * Tests adding an existing field in another content type.
221 public function addExistingField() {
222 // Check "Re-use existing field" appears.
223 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
224 $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
226 // Check that fields of other entity types (here, the 'comment_body' field)
227 // do not show up in the "Re-use existing field" list.
228 $this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
229 // Validate the FALSE assertion above by also testing a valid one.
230 $this->assertTrue($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', [':field_name' => $this->fieldName]), 'The list of options shows a valid option.');
232 // Add a new field based on an existing field.
233 $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $this->fieldLabel . '_2');
237 * Tests the cardinality settings of a field.
239 * We do not test if the number can be submitted with anything else than a
240 * numeric value. That is tested already in FormTest::testNumber().
242 public function cardinalitySettings() {
243 $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/storage';
245 // Assert the cardinality other field cannot be empty when cardinality is
248 'cardinality' => 'number',
249 'cardinality_number' => '',
251 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
252 $this->assertText('Number of values is required.');
254 // Submit a custom number.
256 'cardinality' => 'number',
257 'cardinality_number' => 6,
259 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
260 $this->assertText('Updated field Body field settings.');
261 $this->drupalGet($field_edit_path);
262 $this->assertFieldByXPath("//select[@name='cardinality']", 'number');
263 $this->assertFieldByXPath("//input[@name='cardinality_number']", 6);
265 // Check that tabs displayed.
266 $this->assertLink(t('Edit'));
267 $this->assertLinkByHref('admin/structure/types/manage/article/fields/node.article.body');
268 $this->assertLink(t('Field settings'));
269 $this->assertLinkByHref($field_edit_path);
271 // Add two entries in the body.
272 $edit = ['title[0][value]' => 'Cardinality', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2'];
273 $this->drupalPostForm('node/add/article', $edit, 'Save');
275 // Assert that you can't set the cardinality to a lower number than the
276 // highest delta of this field.
278 'cardinality' => 'number',
279 'cardinality_number' => 1,
281 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
282 $this->assertRaw(t('There is @count entity with @delta or more values in this field.', ['@count' => 1, '@delta' => 2]), 'Correctly failed to set cardinality lower than highest delta.');
284 // Create a second entity with three values.
285 $edit = ['title[0][value]' => 'Cardinality 3', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2', 'body[2][value]' => 'Body 3'];
286 $this->drupalPostForm('node/add/article', $edit, 'Save');
290 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
292 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
293 $this->assertText('Updated field Body field settings.');
294 $this->drupalGet($field_edit_path);
295 $this->assertFieldByXPath("//select[@name='cardinality']", FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
296 $this->assertFieldByXPath("//input[@name='cardinality_number']", 1);
298 // Assert that you can't set the cardinality to a lower number then the
299 // highest delta of this field but can set it to the same.
301 'cardinality' => 'number',
302 'cardinality_number' => 1,
304 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
305 $this->assertRaw(t('There are @count entities with @delta or more values in this field.', ['@count' => 2, '@delta' => 2]), 'Correctly failed to set cardinality lower than highest delta.');
308 'cardinality' => 'number',
309 'cardinality_number' => 2,
311 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
312 $this->assertRaw(t('There is @count entity with @delta or more values in this field.', ['@count' => 1, '@delta' => 3]), 'Correctly failed to set cardinality lower than highest delta.');
315 'cardinality' => 'number',
316 'cardinality_number' => 3,
318 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
322 * Tests deleting a field from the field edit form.
324 protected function deleteField() {
326 $field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
327 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
328 $this->clickLink(t('Delete'));
329 $this->assertResponse(200);
333 * Tests that persistent field storage appears in the field UI.
335 protected function addPersistentFieldStorage() {
336 $field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
337 // Persist the field storage even if there are no fields.
338 $field_storage->set('persist_with_no_fields', TRUE)->save();
339 // Delete all instances of the field.
340 foreach ($field_storage->getBundles() as $node_type) {
341 // Delete all the body field instances.
342 $this->drupalGet('admin/structure/types/manage/' . $node_type . '/fields/node.' . $node_type . '.' . $this->fieldName);
343 $this->clickLink(t('Delete'));
344 $this->drupalPostForm(NULL, [], t('Delete'));
346 // Check "Re-use existing field" appears.
347 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
348 $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
350 // Ensure that we test with a label that contains HTML.
351 $label = $this->randomString(4) . '<br/>' . $this->randomString(4);
352 // Add a new field for the orphaned storage.
353 $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $label);
357 * Asserts field settings are as expected.
360 * The bundle name for the field.
362 * The field name for the field.
365 * @param $entity_type
366 * The entity type for the field.
368 public function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
369 // Assert field storage settings.
370 $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
371 $this->assertTrue($field_storage->getSetting('test_field_storage_setting') == $string, 'Field storage settings were found.');
373 // Assert field settings.
374 $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
375 $this->assertTrue($field->getSetting('test_field_setting') == $string, 'Field settings were found.');
379 * Tests that the 'field_prefix' setting works on Field UI.
381 public function testFieldPrefix() {
382 // Change default field prefix.
383 $field_prefix = strtolower($this->randomMachineName(10));
384 $this->config('field_ui.settings')->set('field_prefix', $field_prefix)->save();
386 // Create a field input and label exceeding the new maxlength, which is 22.
387 $field_exceed_max_length_label = $this->randomString(23);
388 $field_exceed_max_length_input = $this->randomMachineName(23);
390 // Try to create the field.
392 'label' => $field_exceed_max_length_label,
393 'field_name' => $field_exceed_max_length_input,
395 $this->drupalPostForm('admin/structure/types/manage/' . $this->contentType . '/fields/add-field', $edit, t('Save and continue'));
396 $this->assertText('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
398 // Create a valid field.
399 $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
400 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_prefix . $this->fieldNameInput);
401 $this->assertText(format_string('@label settings for @type', ['@label' => $this->fieldLabel, '@type' => $this->contentType]));
405 * Tests that default value is correctly validated and saved.
407 public function testDefaultValue() {
408 // Create a test field storage and field.
409 $field_name = 'test';
410 FieldStorageConfig::create([
411 'field_name' => $field_name,
412 'entity_type' => 'node',
413 'type' => 'test_field',
415 $field = FieldConfig::create([
416 'field_name' => $field_name,
417 'entity_type' => 'node',
418 'bundle' => $this->contentType,
422 entity_get_form_display('node', $this->contentType, 'default')
423 ->setComponent($field_name)
426 $admin_path = 'admin/structure/types/manage/' . $this->contentType . '/fields/' . $field->id();
427 $element_id = "edit-default-value-input-$field_name-0-value";
428 $element_name = "default_value_input[{$field_name}][0][value]";
429 $this->drupalGet($admin_path);
430 $this->assertFieldById($element_id, '', 'The default value widget was empty.');
432 // Check that invalid default values are rejected.
433 $edit = [$element_name => '-1'];
434 $this->drupalPostForm($admin_path, $edit, t('Save settings'));
435 $this->assertText("$field_name does not accept the value -1", 'Form validation failed.');
437 // Check that the default value is saved.
438 $edit = [$element_name => '1'];
439 $this->drupalPostForm($admin_path, $edit, t('Save settings'));
440 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
441 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
442 $this->assertEqual($field->getDefaultValueLiteral(), [['value' => 1]], 'The default value was correctly saved.');
444 // Check that the default value shows up in the form
445 $this->drupalGet($admin_path);
446 $this->assertFieldById($element_id, '1', 'The default value widget was displayed with the correct value.');
448 // Check that the default value can be emptied.
449 $edit = [$element_name => ''];
450 $this->drupalPostForm(NULL, $edit, t('Save settings'));
451 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
452 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
453 $this->assertEqual($field->getDefaultValueLiteral(), [], 'The default value was correctly saved.');
455 // Check that the default value can be empty when the field is marked as
456 // required and can store unlimited values.
457 $field_storage = FieldStorageConfig::loadByName('node', $field_name);
458 $field_storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
459 $field_storage->save();
461 $this->drupalGet($admin_path);
465 $this->drupalPostForm(NULL, $edit, t('Save settings'));
467 $this->drupalGet($admin_path);
468 $this->drupalPostForm(NULL, [], t('Save settings'));
469 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
470 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
471 $this->assertEqual($field->getDefaultValueLiteral(), [], 'The default value was correctly saved.');
473 // Check that the default widget is used when the field is hidden.
474 entity_get_form_display($field->getTargetEntityTypeId(), $field->getTargetBundle(), 'default')
475 ->removeComponent($field_name)->save();
476 $this->drupalGet($admin_path);
477 $this->assertFieldById($element_id, '', 'The default value widget was displayed when field is hidden.');
481 * Tests that deletion removes field storages and fields as expected.
483 public function testDeleteField() {
484 // Create a new field.
485 $bundle_path1 = 'admin/structure/types/manage/' . $this->contentType;
486 $this->fieldUIAddNewField($bundle_path1, $this->fieldNameInput, $this->fieldLabel);
488 // Create an additional node type.
489 $type_name2 = strtolower($this->randomMachineName(8)) . '_test';
490 $type2 = $this->drupalCreateContentType(['name' => $type_name2, 'type' => $type_name2]);
491 $type_name2 = $type2->id();
493 // Add a field to the second node type.
494 $bundle_path2 = 'admin/structure/types/manage/' . $type_name2;
495 $this->fieldUIAddExistingField($bundle_path2, $this->fieldName, $this->fieldLabel);
497 // Delete the first field.
498 $this->fieldUIDeleteField($bundle_path1, "node.$this->contentType.$this->fieldName", $this->fieldLabel, $this->contentType);
500 // Check that the field was deleted.
501 $this->assertNull(FieldConfig::loadByName('node', $this->contentType, $this->fieldName), 'Field was deleted.');
502 // Check that the field storage was not deleted
503 $this->assertNotNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was not deleted.');
505 // Delete the second field.
506 $this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$this->fieldName", $this->fieldLabel, $type_name2);
508 // Check that the field was deleted.
509 $this->assertNull(FieldConfig::loadByName('node', $type_name2, $this->fieldName), 'Field was deleted.');
510 // Check that the field storage was deleted too.
511 $this->assertNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was deleted.');
515 * Tests that Field UI respects disallowed field names.
517 public function testDisallowedFieldNames() {
518 // Reset the field prefix so we can test properly.
519 $this->config('field_ui.settings')->set('field_prefix', '')->save();
521 $label = 'Disallowed field';
524 'new_storage_type' => 'test_field',
527 // Try with an entity key.
528 $edit['field_name'] = 'title';
529 $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
530 $this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
531 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
533 // Try with a base field.
534 $edit['field_name'] = 'sticky';
535 $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
536 $this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
537 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
541 * Tests that Field UI respects locked fields.
543 public function testLockedField() {
544 // Create a locked field and attach it to a bundle. We need to do this
545 // programmatically as there's no way to create a locked field through UI.
546 $field_name = strtolower($this->randomMachineName(8));
547 $field_storage = FieldStorageConfig::create([
548 'field_name' => $field_name,
549 'entity_type' => 'node',
550 'type' => 'test_field',
554 $field_storage->save();
555 FieldConfig::create([
556 'field_storage' => $field_storage,
557 'bundle' => $this->contentType,
559 entity_get_form_display('node', $this->contentType, 'default')
560 ->setComponent($field_name, [
561 'type' => 'test_field_widget',
565 // Check that the links for edit and delete are not present.
566 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
567 $locked = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
568 $this->assertSame('Locked', $locked[0]->getHtml(), 'Field is marked as Locked in the UI');
569 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_name . '/delete');
570 $this->assertResponse(403);
574 * Tests that Field UI respects the 'no_ui' flag in the field type definition.
576 public function testHiddenFields() {
577 // Check that the field type is not available in the 'add new field' row.
578 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
579 $this->assertFalse($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="hidden_test_field"]'), "The 'add new field' select respects field types 'no_ui' property.");
580 $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="shape"]'), "The 'add new field' select shows a valid option.");
582 // Create a field storage and a field programmatically.
583 $field_name = 'hidden_test_field';
584 FieldStorageConfig::create([
585 'field_name' => $field_name,
586 'entity_type' => 'node',
587 'type' => $field_name,
590 'field_name' => $field_name,
591 'bundle' => $this->contentType,
592 'entity_type' => 'node',
593 'label' => t('Hidden field'),
595 FieldConfig::create($field)->save();
596 entity_get_form_display('node', $this->contentType, 'default')
597 ->setComponent($field_name)
599 $this->assertTrue(FieldConfig::load('node.' . $this->contentType . '.' . $field_name), format_string('A field of the field storage %field was created programmatically.', ['%field' => $field_name]));
601 // Check that the newly added field appears on the 'Manage Fields'
603 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
604 $this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="hidden-test-field"]//td[1]', $field['label'], 'Field was created and appears in the overview page.');
606 // Check that the field does not appear in the 're-use existing field' row
608 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
609 $this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', [':field_name' => $field_name]), "The 're-use existing field' select respects field types 'no_ui' property.");
610 $this->assertTrue($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value=:field_name]', [':field_name' => 'field_tags']), "The 're-use existing field' select shows a valid option.");
612 // Check that non-configurable fields are not available.
613 $field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
614 foreach ($field_types as $field_type => $definition) {
615 if (empty($definition['no_ui'])) {
616 $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', [':field_type' => $field_type]), new FormattableMarkup('Configurable field type @field_type is available.', ['@field_type' => $field_type]));
619 $this->assertFalse($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', [':field_type' => $field_type]), new FormattableMarkup('Non-configurable field type @field_type is not available.', ['@field_type' => $field_type]));
625 * Tests that a duplicate field name is caught by validation.
627 public function testDuplicateFieldName() {
628 // field_tags already exists, so we're expecting an error when trying to
629 // create a new field with the same name.
631 'field_name' => 'tags',
632 'label' => $this->randomMachineName(),
633 'new_storage_type' => 'entity_reference',
635 $url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
636 $this->drupalPostForm($url, $edit, t('Save and continue'));
638 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
639 $this->assertUrl($url, [], 'Stayed on the same page.');
643 * Tests that external URLs in the 'destinations' query parameter are blocked.
645 public function testExternalDestinations() {
647 'query' => ['destinations' => ['http://example.com']],
649 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.body/storage', [], 'Save field settings', $options);
650 // The external redirect should not fire.
651 $this->assertUrl('admin/structure/types/manage/article/fields/node.article.body/storage', $options);
652 $this->assertResponse(200);
653 $this->assertRaw('Attempt to update field <em class="placeholder">Body</em> failed: <em class="placeholder">The internal path component 'http://example.com' is external. You are not allowed to specify an external URL together with internal:/.</em>.');
657 * Tests that deletion removes field storages and fields as expected for a term.
659 public function testDeleteTaxonomyField() {
660 // Create a new field.
661 $bundle_path = 'admin/structure/taxonomy/manage/tags/overview';
663 $this->fieldUIAddNewField($bundle_path, $this->fieldNameInput, $this->fieldLabel);
666 $this->fieldUIDeleteField($bundle_path, "taxonomy_term.tags.$this->fieldName", $this->fieldLabel, 'Tags');
668 // Check that the field was deleted.
669 $this->assertNull(FieldConfig::loadByName('taxonomy_term', 'tags', $this->fieldName), 'Field was deleted.');
670 // Check that the field storage was deleted too.
671 $this->assertNull(FieldStorageConfig::loadByName('taxonomy_term', $this->fieldName), 'Field storage was deleted.');
675 * Tests that help descriptions render valid HTML.
677 public function testHelpDescriptions() {
678 // Create an image field
679 FieldStorageConfig::create([
680 'field_name' => 'field_image',
681 'entity_type' => 'node',
685 FieldConfig::create([
686 'field_name' => 'field_image',
687 'entity_type' => 'node',
689 'bundle' => 'article',
692 entity_get_form_display('node', 'article', 'default')->setComponent('field_image')->save();
695 'description' => '<strong>Test with an upload field.',
697 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_image', $edit, t('Save settings'));
699 // Check that hook_field_widget_form_alter() does believe this is the
700 // default value form.
701 $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_tags');
702 $this->assertText('From hook_field_widget_form_alter(): Default form is true.', 'Default value form in hook_field_widget_form_alter().');
705 'description' => '<em>Test with a non upload field.',
707 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_tags', $edit, t('Save settings'));
709 $this->drupalGet('node/add/article');
710 $this->assertRaw('<strong>Test with an upload field.</strong>');
711 $this->assertRaw('<em>Test with a non upload field.</em>');
715 * Tests that the field list administration page operates correctly.
717 public function fieldListAdminPage() {
718 $this->drupalGet('admin/reports/fields');
719 $this->assertText($this->fieldName, 'Field name is displayed in field list.');
720 $this->assertLinkByHref('admin/structure/types/manage/' . $this->contentType . '/fields');
724 * Tests the "preconfigured field" functionality.
726 * @see \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface
728 public function testPreconfiguredFields() {
729 $this->drupalGet('admin/structure/types/manage/article/fields/add-field');
731 // Check that the preconfigured field option exist alongside the regular
732 // field type option.
733 $this->assertOption('edit-new-storage-type', 'field_ui:test_field_with_preconfigured_options:custom_options');
734 $this->assertOption('edit-new-storage-type', 'test_field_with_preconfigured_options');
736 // Add a field with every possible preconfigured value.
737 $this->fieldUIAddNewField(NULL, 'test_custom_options', 'Test label', 'field_ui:test_field_with_preconfigured_options:custom_options');
738 $field_storage = FieldStorageConfig::loadByName('node', 'field_test_custom_options');
739 $this->assertEqual($field_storage->getCardinality(), FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
740 $this->assertEqual($field_storage->getSetting('test_field_storage_setting'), 'preconfigured_storage_setting');
742 $field = FieldConfig::loadByName('node', 'article', 'field_test_custom_options');
743 $this->assertTrue($field->isRequired());
744 $this->assertEqual($field->getSetting('test_field_setting'), 'preconfigured_field_setting');
746 $form_display = entity_get_form_display('node', 'article', 'default');
747 $this->assertEqual($form_display->getComponent('field_test_custom_options')['type'], 'test_field_widget_multiple');
748 $view_display = entity_get_display('node', 'article', 'default');
749 $this->assertEqual($view_display->getComponent('field_test_custom_options')['type'], 'field_test_multiple');
750 $this->assertEqual($view_display->getComponent('field_test_custom_options')['settings']['test_formatter_setting_multiple'], 'altered dummy test string');
754 * Tests the access to non-existent field URLs.
756 public function testNonExistentFieldUrls() {
757 $field_id = 'node.foo.bar';
759 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
760 $this->assertResponse(404);
762 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
763 $this->assertResponse(404);