Newer
Older
<?php
namespace Drupal\field_ui\Tests;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
Alex Pott
committed
use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
* Tests the Field UI "Manage fields" screen.
*
* @group field_ui
class ManageFieldsTest extends WebTestBase {
Angie Byron
committed
use EntityReferenceTestTrait;
Alex Pott
committed
* Modules to install.
*
* @var array
*/
public static $modules = ['node', 'field_ui', 'field_test', 'taxonomy', 'image', 'block'];
Alex Pott
committed
/**
Angie Byron
committed
* The ID of the custom content type created for testing.
Alex Pott
committed
*
Angie Byron
committed
* @var string
Alex Pott
committed
*/
protected $contentType;
/**
* The label for a random field to be created for testing.
*
* @var string
*/
protected $fieldLabel;
/**
* The input name of a random field to be created for testing.
*
* @var string
*/
protected $fieldNameInput;
/**
* The name of a random field to be created for testing.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
Alex Pott
committed
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('system_breadcrumb_block');
$this->drupalPlaceBlock('local_actions_block');
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('page_title_block');
// Create a test user.
$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']);
$this->drupalLogin($admin_user);
// Create content type, with underscores.
$type_name = strtolower($this->randomMachineName(8)) . '_test';
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
Alex Pott
committed
$this->contentType = $type->id();
// Create random field name with markup to test escaping.
$this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
Alex Pott
committed
$this->fieldNameInput = strtolower($this->randomMachineName(8));
$this->fieldName = 'field_' . $this->fieldNameInput;
// Create Basic page and Article node types.
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
// Create a vocabulary named "Tags".
$vocabulary = Vocabulary::create([
'name' => 'Tags',
'vid' => 'tags',
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
]);
$vocabulary->save();
$handler_settings = [
'target_bundles' => [
Angie Byron
committed
$vocabulary->id() => $vocabulary->id(),
],
];
Angie Byron
committed
$this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
Alex Pott
committed
entity_get_form_display('node', 'article', 'default')
->setComponent('field_' . $vocabulary->id())
->save();
}
/**
* Runs the field CRUD tests.
*
* In order to act on the same fields, and not create the fields over and over
* again the following tests create, update and delete the same fields.
*/
public function testCRUDFields() {
$this->manageFieldsPage();
$this->createField();
$this->updateField();
$this->addExistingField();
$this->cardinalitySettings();
$this->fieldListAdminPage();
$this->deleteField();
$this->addPersistentFieldStorage();
}
/**
* Tests the manage fields page.
*
* @param string $type
* (optional) The name of a content type.
public function manageFieldsPage($type = '') {
Alex Pott
committed
$type = empty($type) ? $this->contentType : $type;
$this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
// Check all table columns.
$table_headers = [
t('Label'),
t('Machine name'),
t('Field type'),
t('Operations'),
];
foreach ($table_headers as $table_header) {
// We check that the label appear in the table headings.
$this->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', ['%table_header' => $table_header]));
Angie Byron
committed
// Test the "Add field" action link.
$this->assertLink('Add field');
// Assert entity operations for all fields.
Angie Byron
committed
$number_of_links = 3;
$number_of_links_found = 0;
$operation_links = $this->xpath('//ul[@class = "dropbutton"]/li/a');
$url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body";
Angie Byron
committed
foreach ($operation_links as $link) {
switch ($link['title']) {
case 'Edit field settings.':
$this->assertIdentical($url, (string) $link['href']);
$number_of_links_found++;
break;
case 'Edit storage settings.':
$this->assertIdentical("$url/storage", (string) $link['href']);
$number_of_links_found++;
break;
case 'Delete field.':
$this->assertIdentical("$url/delete", (string) $link['href']);
$number_of_links_found++;
break;
}
}
$this->assertEqual($number_of_links, $number_of_links_found);
}
/**
* Tests adding a new field.
*
* @todo Assert properties can bet set in the form and read back in
* $field_storage and $fields.
public function createField() {
// Create a test field.
Alex Pott
committed
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
}
/**
* Tests editing an existing field.
*/
public function updateField() {
Alex Pott
committed
$field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
// Go to the field edit page.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
$this->assertEscaped($this->fieldLabel);
// Populate the field settings with new settings.
$string = 'updated dummy test string';
$edit = [
Alex Pott
committed
'settings[test_field_storage_setting]' => $string,
];
$this->drupalPostForm(NULL, $edit, t('Save field settings'));
// Go to the field edit page.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
$edit = [
Alex Pott
committed
'settings[test_field_setting]' => $string,
];
Alex Pott
committed
$this->assertText(t('Default value'), 'Default value heading is shown');
$this->drupalPostForm(NULL, $edit, t('Save settings'));
// Assert the field settings are correct.
Alex Pott
committed
$this->assertFieldSettings($this->contentType, $this->fieldName, $string);
// Assert redirection back to the "manage fields" page.
Alex Pott
committed
$this->assertUrl('admin/structure/types/manage/' . $this->contentType . '/fields');
}
/**
* Tests adding an existing field in another content type.
*/
public function addExistingField() {
Dries Buytaert
committed
// Check "Re-use existing field" appears.
Angie Byron
committed
$this->drupalGet('admin/structure/types/manage/page/fields/add-field');
$this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
// Check that fields of other entity types (here, the 'comment_body' field)
// do not show up in the "Re-use existing field" list.
Angie Byron
committed
$this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
// Validate the FALSE assertion above by also testing a valid one.
$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.');
// Add a new field based on an existing field.
Alex Pott
committed
$this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $this->fieldLabel . '_2');
/**
* Tests the cardinality settings of a field.
*
* We do not test if the number can be submitted with anything else than a
* numeric value. That is tested already in FormTest::testNumber().
*/
public function cardinalitySettings() {
$field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/storage';
// Assert the cardinality other field cannot be empty when cardinality is
// set to 'number'.
$edit = [
Alex Pott
committed
'cardinality' => 'number',
'cardinality_number' => '',
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$this->assertText('Number of values is required.');
// Submit a custom number.
$edit = [
Alex Pott
committed
'cardinality' => 'number',
'cardinality_number' => 6,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$this->assertText('Updated field Body field settings.');
$this->drupalGet($field_edit_path);
Alex Pott
committed
$this->assertFieldByXPath("//select[@name='cardinality']", 'number');
$this->assertFieldByXPath("//input[@name='cardinality_number']", 6);
catch
committed
// Check that tabs displayed.
$this->assertLink(t('Edit'));
$this->assertLinkByHref('admin/structure/types/manage/article/fields/node.article.body');
$this->assertLink(t('Field settings'));
$this->assertLinkByHref($field_edit_path);
// Add two entries in the body.
$edit = ['title[0][value]' => 'Cardinality', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2'];
$this->drupalPostForm('node/add/article', $edit, 'Save');
// Assert that you can't set the cardinality to a lower number than the
// highest delta of this field.
$edit = [
'cardinality' => 'number',
'cardinality_number' => 1,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$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.');
// Create a second entity with three values.
$edit = ['title[0][value]' => 'Cardinality 3', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2', 'body[2][value]' => 'Body 3'];
$this->drupalPostForm('node/add/article', $edit, 'Save');
// Set to unlimited.
$edit = [
Alex Pott
committed
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$this->assertText('Updated field Body field settings.');
$this->drupalGet($field_edit_path);
Alex Pott
committed
$this->assertFieldByXPath("//select[@name='cardinality']", FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->assertFieldByXPath("//input[@name='cardinality_number']", 1);
// Assert that you can't set the cardinality to a lower number then the
// highest delta of this field but can set it to the same.
$edit = [
'cardinality' => 'number',
'cardinality_number' => 1,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$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.');
$edit = [
'cardinality' => 'number',
'cardinality_number' => 2,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
$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.');
$edit = [
'cardinality' => 'number',
'cardinality_number' => 3,
];
$this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
}
/**
* Tests deleting a field from the field edit form.
*/
protected function deleteField() {
// Delete the field.
Alex Pott
committed
$field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
Alex Pott
committed
$this->clickLink(t('Delete'));
$this->assertResponse(200);
}
/**
* Tests that persistent field storage appears in the field UI.
*/
protected function addPersistentFieldStorage() {
Alex Pott
committed
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
// Persist the field storage even if there are no fields.
$field_storage->set('persist_with_no_fields', TRUE)->save();
// Delete all instances of the field.
foreach ($field_storage->getBundles() as $node_type) {
// Delete all the body field instances.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $node_type . '/fields/node.' . $node_type . '.' . $this->fieldName);
$this->clickLink(t('Delete'));
$this->drupalPostForm(NULL, [], t('Delete'));
}
// Check "Re-use existing field" appears.
Angie Byron
committed
$this->drupalGet('admin/structure/types/manage/page/fields/add-field');
$this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
// Ensure that we test with a label that contains HTML.
$label = $this->randomString(4) . '<br/>' . $this->randomString(4);
// Add a new field for the orphaned storage.
$this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $label);
/**
* Asserts field settings are as expected.
*
* @param $bundle
* The bundle name for the field.
* @param $field_name
* The field name for the field.
* @param $string
* The settings text.
* @param $entity_type
* The entity type for the field.
public function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
// Assert field storage settings.
$field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
$this->assertTrue($field_storage->getSetting('test_field_storage_setting') == $string, 'Field storage settings were found.');
// Assert field settings.
$field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
$this->assertTrue($field->getSetting('test_field_setting') == $string, 'Field settings were found.');
Alex Pott
committed
/**
* Tests that the 'field_prefix' setting works on Field UI.
*/
public function testFieldPrefix() {
Alex Pott
committed
// Change default field prefix.
Alex Pott
committed
$field_prefix = strtolower($this->randomMachineName(10));
$this->config('field_ui.settings')->set('field_prefix', $field_prefix)->save();
Alex Pott
committed
// Create a field input and label exceeding the new maxlength, which is 22.
$field_exceed_max_length_label = $this->randomString(23);
Alex Pott
committed
$field_exceed_max_length_input = $this->randomMachineName(23);
Alex Pott
committed
// Try to create the field.
$edit = [
Angie Byron
committed
'label' => $field_exceed_max_length_label,
'field_name' => $field_exceed_max_length_input,
];
Alex Pott
committed
$this->drupalPostForm('admin/structure/types/manage/' . $this->contentType . '/fields/add-field', $edit, t('Save and continue'));
Angie Byron
committed
$this->assertText('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
Alex Pott
committed
// Create a valid field.
Alex Pott
committed
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_prefix . $this->fieldNameInput);
$this->assertText(format_string('@label settings for @type', ['@label' => $this->fieldLabel, '@type' => $this->contentType]));
Alex Pott
committed
}
/**
* Tests that default value is correctly validated and saved.
*/
public function testDefaultValue() {
// Create a test field storage and field.
$field_name = 'test';
FieldStorageConfig::create([
Alex Pott
committed
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'test_field'
])->save();
$field = FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'node',
Alex Pott
committed
'bundle' => $this->contentType,
]);
$field->save();
Alex Pott
committed
entity_get_form_display('node', $this->contentType, 'default')
Alex Pott
committed
->setComponent($field_name)
->save();
Alex Pott
committed
$admin_path = 'admin/structure/types/manage/' . $this->contentType . '/fields/' . $field->id();
Angie Byron
committed
$element_id = "edit-default-value-input-$field_name-0-value";
$element_name = "default_value_input[{$field_name}][0][value]";
$this->drupalGet($admin_path);
Jennifer Hodgdon
committed
$this->assertFieldById($element_id, '', 'The default value widget was empty.');
// Check that invalid default values are rejected.
$edit = [$element_name => '-1'];
$this->drupalPostForm($admin_path, $edit, t('Save settings'));
$this->assertText("$field_name does not accept the value -1", 'Form validation failed.');
// Check that the default value is saved.
$edit = [$element_name => '1'];
$this->drupalPostForm($admin_path, $edit, t('Save settings'));
Jennifer Hodgdon
committed
$this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Alex Pott
committed
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
$this->assertEqual($field->getDefaultValueLiteral(), [['value' => 1]], 'The default value was correctly saved.');
// Check that the default value shows up in the form
$this->drupalGet($admin_path);
Jennifer Hodgdon
committed
$this->assertFieldById($element_id, '1', 'The default value widget was displayed with the correct value.');
// Check that the default value can be emptied.
$edit = [$element_name => ''];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
Jennifer Hodgdon
committed
$this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Alex Pott
committed
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
$this->assertEqual($field->getDefaultValueLiteral(), NULL, 'The default value was correctly saved.');
Angie Byron
committed
// Check that the default value can be empty when the field is marked as
// required and can store unlimited values.
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
$field_storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$field_storage->save();
$this->drupalGet($admin_path);
$edit = [
Alex Pott
committed
'required' => 1,
];
$this->drupalPostForm(NULL, $edit, t('Save settings'));
$this->drupalGet($admin_path);
$this->drupalPostForm(NULL, [], t('Save settings'));
$this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Alex Pott
committed
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
$this->assertEqual($field->getDefaultValueLiteral(), NULL, 'The default value was correctly saved.');
Angie Byron
committed
// Check that the default widget is used when the field is hidden.
entity_get_form_display($field->getTargetEntityTypeId(), $field->getTargetBundle(), 'default')
Angie Byron
committed
->removeComponent($field_name)->save();
Angie Byron
committed
$this->drupalGet($admin_path);
Angie Byron
committed
$this->assertFieldById($element_id, '', 'The default value widget was displayed when field is hidden.');
}
/**
* Tests that deletion removes field storages and fields as expected.
public function testDeleteField() {
// Create a new field.
Alex Pott
committed
$bundle_path1 = 'admin/structure/types/manage/' . $this->contentType;
$this->fieldUIAddNewField($bundle_path1, $this->fieldNameInput, $this->fieldLabel);
// Create an additional node type.
Alex Pott
committed
$type_name2 = strtolower($this->randomMachineName(8)) . '_test';
$type2 = $this->drupalCreateContentType(['name' => $type_name2, 'type' => $type_name2]);
$type_name2 = $type2->id();
// Add a field to the second node type.
$bundle_path2 = 'admin/structure/types/manage/' . $type_name2;
Alex Pott
committed
$this->fieldUIAddExistingField($bundle_path2, $this->fieldName, $this->fieldLabel);
// Delete the first field.
Alex Pott
committed
$this->fieldUIDeleteField($bundle_path1, "node.$this->contentType.$this->fieldName", $this->fieldLabel, $this->contentType);
// Check that the field was deleted.
Alex Pott
committed
$this->assertNull(FieldConfig::loadByName('node', $this->contentType, $this->fieldName), 'Field was deleted.');
// Check that the field storage was not deleted
Alex Pott
committed
$this->assertNotNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was not deleted.');
// Delete the second field.
Alex Pott
committed
$this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$this->fieldName", $this->fieldLabel, $type_name2);
// Check that the field was deleted.
Alex Pott
committed
$this->assertNull(FieldConfig::loadByName('node', $type_name2, $this->fieldName), 'Field was deleted.');
// Check that the field storage was deleted too.
Alex Pott
committed
$this->assertNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was deleted.');
Alex Pott
committed
/**
* Tests that Field UI respects disallowed field names.
*/
public function testDisallowedFieldNames() {
Alex Pott
committed
// Reset the field prefix so we can test properly.
$this->config('field_ui.settings')->set('field_prefix', '')->save();
Alex Pott
committed
$label = 'Disallowed field';
$edit = [
Angie Byron
committed
'label' => $label,
'new_storage_type' => 'test_field',
];
Alex Pott
committed
// Try with an entity key.
Angie Byron
committed
$edit['field_name'] = 'title';
Alex Pott
committed
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
Alex Pott
committed
$this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
Alex Pott
committed
$this->assertText(t('The machine-readable name is already in use. It must be unique.'));
Alex Pott
committed
// Try with a base field.
Angie Byron
committed
$edit['field_name'] = 'sticky';
Alex Pott
committed
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
Alex Pott
committed
$this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
Alex Pott
committed
$this->assertText(t('The machine-readable name is already in use. It must be unique.'));
Alex Pott
committed
}
/**
* Tests that Field UI respects locked fields.
*/
public function testLockedField() {
// Create a locked field and attach it to a bundle. We need to do this
Alex Pott
committed
// programmatically as there's no way to create a locked field through UI.
Alex Pott
committed
$field_name = strtolower($this->randomMachineName(8));
$field_storage = FieldStorageConfig::create([
Alex Pott
committed
'field_name' => $field_name,
'entity_type' => 'node',
'type' => 'test_field',
'cardinality' => 1,
'locked' => TRUE
]);
$field_storage->save();
FieldConfig::create([
'field_storage' => $field_storage,
Alex Pott
committed
'bundle' => $this->contentType,
])->save();
Alex Pott
committed
entity_get_form_display('node', $this->contentType, 'default')
->setComponent($field_name, [
Alex Pott
committed
'type' => 'test_field_widget',
])
Alex Pott
committed
->save();
// Check that the links for edit and delete are not present.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
$locked = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
$this->assertTrue(in_array('Locked', $locked), 'Field is marked as Locked in the UI');
$edit_link = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
$this->assertFalse(in_array('edit', $edit_link), 'Edit option for locked field is not present the UI');
$delete_link = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
$this->assertFalse(in_array('delete', $delete_link), 'Delete option for locked field is not present the UI');
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_name . '/delete');
Angie Byron
committed
$this->assertResponse(403);
}
* Tests that Field UI respects the 'no_ui' flag in the field type definition.
public function testHiddenFields() {
// Check that the field type is not available in the 'add new field' row.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
Angie Byron
committed
$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.");
$this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="shape"]'), "The 'add new field' select shows a valid option.");
// Create a field storage and a field programmatically.
$field_name = 'hidden_test_field';
FieldStorageConfig::create([
Alex Pott
committed
'field_name' => $field_name,
'entity_type' => 'node',
'type' => $field_name,
])->save();
$field = [
'field_name' => $field_name,
Alex Pott
committed
'bundle' => $this->contentType,
'entity_type' => 'node',
'label' => t('Hidden field'),
];
FieldConfig::create($field)->save();
Alex Pott
committed
entity_get_form_display('node', $this->contentType, 'default')
Alex Pott
committed
->setComponent($field_name)
->save();
$this->assertTrue(FieldConfig::load('node.' . $this->contentType . '.' . $field_name), format_string('A field of the field storage %field was created programmatically.', ['%field' => $field_name]));
// Check that the newly added field appears on the 'Manage Fields'
// screen.
Alex Pott
committed
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
$this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="hidden-test-field"]//td[1]', $field['label'], 'Field was created and appears in the overview page.');
// Check that the field does not appear in the 're-use existing field' row
// on other bundles.
Angie Byron
committed
$this->drupalGet('admin/structure/types/manage/page/fields/add-field');
$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.");
$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.");
Alex Pott
committed
// Check that non-configurable fields are not available.
Alex Pott
committed
$field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
Alex Pott
committed
foreach ($field_types as $field_type => $definition) {
Angie Byron
committed
if (empty($definition['no_ui'])) {
$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]));
Alex Pott
committed
}
else {
$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]));
Alex Pott
committed
}
}
}
/**
* Tests that a duplicate field name is caught by validation.
*/
public function testDuplicateFieldName() {
// field_tags already exists, so we're expecting an error when trying to
// create a new field with the same name.
$edit = [
Angie Byron
committed
'field_name' => 'tags',
'label' => $this->randomMachineName(),
Angie Byron
committed
'new_storage_type' => 'entity_reference',
];
Alex Pott
committed
$url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
Angie Byron
committed
$this->drupalPostForm($url, $edit, t('Save and continue'));
$this->assertText(t('The machine-readable name is already in use. It must be unique.'));
$this->assertUrl($url, [], 'Stayed on the same page.');
/**
* Tests that external URLs in the 'destinations' query parameter are blocked.
*/
public function testExternalDestinations() {
$options = [
'query' => ['destinations' => ['http://example.com']],
];
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.body/storage', [], 'Save field settings', $options);
// The external redirect should not fire.
$this->assertUrl('admin/structure/types/manage/article/fields/node.article.body/storage', $options);
$this->assertResponse(200);
catch
committed
$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>.');
}
Angie Byron
committed
/**
* Tests that deletion removes field storages and fields as expected for a term.
Angie Byron
committed
*/
public function testDeleteTaxonomyField() {
Angie Byron
committed
// Create a new field.
Alex Pott
committed
$bundle_path = 'admin/structure/taxonomy/manage/tags/overview';
Alex Pott
committed
$this->fieldUIAddNewField($bundle_path, $this->fieldNameInput, $this->fieldLabel);
Angie Byron
committed
// Delete the field.
Alex Pott
committed
$this->fieldUIDeleteField($bundle_path, "taxonomy_term.tags.$this->fieldName", $this->fieldLabel, 'Tags');
Angie Byron
committed
// Check that the field was deleted.
Alex Pott
committed
$this->assertNull(FieldConfig::loadByName('taxonomy_term', 'tags', $this->fieldName), 'Field was deleted.');
// Check that the field storage was deleted too.
Alex Pott
committed
$this->assertNull(FieldStorageConfig::loadByName('taxonomy_term', $this->fieldName), 'Field storage was deleted.');
Angie Byron
committed
}
Alex Pott
committed
/**
* Tests that help descriptions render valid HTML.
*/
public function testHelpDescriptions() {
Alex Pott
committed
// Create an image field
FieldStorageConfig::create([
Alex Pott
committed
'field_name' => 'field_image',
'entity_type' => 'node',
Alex Pott
committed
'type' => 'image',
])->save();
Alex Pott
committed
FieldConfig::create([
Alex Pott
committed
'field_name' => 'field_image',
'entity_type' => 'node',
'label' => 'Image',
'bundle' => 'article',
])->save();
Alex Pott
committed
entity_get_form_display('node', 'article', 'default')->setComponent('field_image')->save();
$edit = [
Alex Pott
committed
'description' => '<strong>Test with an upload field.',
];
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_image', $edit, t('Save settings'));
Alex Pott
committed
// Check that hook_field_widget_form_alter() does believe this is the
// default value form.
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_tags');
$this->assertText('From hook_field_widget_form_alter(): Default form is true.', 'Default value form in hook_field_widget_form_alter().');
$edit = [
Alex Pott
committed
'description' => '<em>Test with a non upload field.',
];
$this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_tags', $edit, t('Save settings'));
Alex Pott
committed
$this->drupalGet('node/add/article');
$this->assertRaw('<strong>Test with an upload field.</strong>');
$this->assertRaw('<em>Test with a non upload field.</em>');
}
/**
* Tests that the field list administration page operates correctly.
*/
public function fieldListAdminPage() {
$this->drupalGet('admin/reports/fields');
Alex Pott
committed
$this->assertText($this->fieldName, 'Field name is displayed in field list.');
$this->assertTrue($this->assertLinkByHref('admin/structure/types/manage/' . $this->contentType . '/fields'), 'Link to content type using field is displayed in field list.');
Angie Byron
committed
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
/**
* Tests the "preconfigured field" functionality.
*
* @see \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface
*/
public function testPreconfiguredFields() {
$this->drupalGet('admin/structure/types/manage/article/fields/add-field');
// Check that the preconfigured field option exist alongside the regular
// field type option.
$this->assertOption('edit-new-storage-type', 'field_ui:test_field_with_preconfigured_options:custom_options');
$this->assertOption('edit-new-storage-type', 'test_field_with_preconfigured_options');
// Add a field with every possible preconfigured value.
$this->fieldUIAddNewField(NULL, 'test_custom_options', 'Test label', 'field_ui:test_field_with_preconfigured_options:custom_options');
$field_storage = FieldStorageConfig::loadByName('node', 'field_test_custom_options');
$this->assertEqual($field_storage->getCardinality(), FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$this->assertEqual($field_storage->getSetting('test_field_storage_setting'), 'preconfigured_storage_setting');
$field = FieldConfig::loadByName('node', 'article', 'field_test_custom_options');
$this->assertTrue($field->isRequired());
$this->assertEqual($field->getSetting('test_field_setting'), 'preconfigured_field_setting');
$form_display = entity_get_form_display('node', 'article', 'default');
$this->assertEqual($form_display->getComponent('field_test_custom_options')['type'], 'test_field_widget_multiple');
$view_display = entity_get_display('node', 'article', 'default');
$this->assertEqual($view_display->getComponent('field_test_custom_options')['type'], 'field_test_multiple');
Lee Rowlands
committed
$this->assertEqual($view_display->getComponent('field_test_custom_options')['settings']['test_formatter_setting_multiple'], 'altered dummy test string');
Angie Byron
committed
}
Alex Pott
committed
/**
* Tests the access to non-existent field URLs.
*/
public function testNonExistentFieldUrls() {
$field_id = 'node.foo.bar';
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
$this->assertResponse(404);
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
$this->assertResponse(404);
}