annotate core/modules/field_ui/src/Tests/ManageFieldsTest.php @ 6:875880e46745

Styling
author Chris Cannam
date Fri, 08 Dec 2017 13:21:27 +0000
parents 4c8ae668cc8c
children 1fec387a4317
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\field_ui\Tests;
Chris@0 4
Chris@0 5 use Drupal\Component\Utility\SafeMarkup;
Chris@0 6 use Drupal\Core\Field\FieldStorageDefinitionInterface;
Chris@0 7 use Drupal\Core\Language\LanguageInterface;
Chris@0 8 use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
Chris@0 9 use Drupal\field\Entity\FieldConfig;
Chris@0 10 use Drupal\field\Entity\FieldStorageConfig;
Chris@0 11 use Drupal\simpletest\WebTestBase;
Chris@0 12 use Drupal\taxonomy\Entity\Vocabulary;
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Tests the Field UI "Manage fields" screen.
Chris@0 16 *
Chris@0 17 * @group field_ui
Chris@0 18 */
Chris@0 19 class ManageFieldsTest extends WebTestBase {
Chris@0 20
Chris@0 21 use FieldUiTestTrait;
Chris@0 22 use EntityReferenceTestTrait;
Chris@0 23
Chris@0 24 /**
Chris@0 25 * Modules to install.
Chris@0 26 *
Chris@0 27 * @var array
Chris@0 28 */
Chris@0 29 public static $modules = ['node', 'field_ui', 'field_test', 'taxonomy', 'image', 'block'];
Chris@0 30
Chris@0 31 /**
Chris@0 32 * The ID of the custom content type created for testing.
Chris@0 33 *
Chris@0 34 * @var string
Chris@0 35 */
Chris@0 36 protected $contentType;
Chris@0 37
Chris@0 38 /**
Chris@0 39 * The label for a random field to be created for testing.
Chris@0 40 *
Chris@0 41 * @var string
Chris@0 42 */
Chris@0 43 protected $fieldLabel;
Chris@0 44
Chris@0 45 /**
Chris@0 46 * The input name of a random field to be created for testing.
Chris@0 47 *
Chris@0 48 * @var string
Chris@0 49 */
Chris@0 50 protected $fieldNameInput;
Chris@0 51
Chris@0 52 /**
Chris@0 53 * The name of a random field to be created for testing.
Chris@0 54 *
Chris@0 55 * @var string
Chris@0 56 */
Chris@0 57 protected $fieldName;
Chris@0 58
Chris@0 59 /**
Chris@0 60 * {@inheritdoc}
Chris@0 61 */
Chris@0 62 protected function setUp() {
Chris@0 63 parent::setUp();
Chris@0 64
Chris@0 65 $this->drupalPlaceBlock('system_breadcrumb_block');
Chris@0 66 $this->drupalPlaceBlock('local_actions_block');
Chris@0 67 $this->drupalPlaceBlock('local_tasks_block');
Chris@0 68 $this->drupalPlaceBlock('page_title_block');
Chris@0 69
Chris@0 70 // Create a test user.
Chris@0 71 $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']);
Chris@0 72 $this->drupalLogin($admin_user);
Chris@0 73
Chris@0 74 // Create content type, with underscores.
Chris@0 75 $type_name = strtolower($this->randomMachineName(8)) . '_test';
Chris@0 76 $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
Chris@0 77 $this->contentType = $type->id();
Chris@0 78
Chris@0 79 // Create random field name with markup to test escaping.
Chris@0 80 $this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
Chris@0 81 $this->fieldNameInput = strtolower($this->randomMachineName(8));
Chris@0 82 $this->fieldName = 'field_' . $this->fieldNameInput;
Chris@0 83
Chris@0 84 // Create Basic page and Article node types.
Chris@0 85 $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
Chris@0 86 $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
Chris@0 87
Chris@0 88 // Create a vocabulary named "Tags".
Chris@0 89 $vocabulary = Vocabulary::create([
Chris@0 90 'name' => 'Tags',
Chris@0 91 'vid' => 'tags',
Chris@0 92 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
Chris@0 93 ]);
Chris@0 94 $vocabulary->save();
Chris@0 95
Chris@0 96 $handler_settings = [
Chris@0 97 'target_bundles' => [
Chris@0 98 $vocabulary->id() => $vocabulary->id(),
Chris@0 99 ],
Chris@0 100 ];
Chris@0 101 $this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
Chris@0 102
Chris@0 103 entity_get_form_display('node', 'article', 'default')
Chris@0 104 ->setComponent('field_' . $vocabulary->id())
Chris@0 105 ->save();
Chris@0 106 }
Chris@0 107
Chris@0 108 /**
Chris@0 109 * Runs the field CRUD tests.
Chris@0 110 *
Chris@0 111 * In order to act on the same fields, and not create the fields over and over
Chris@0 112 * again the following tests create, update and delete the same fields.
Chris@0 113 */
Chris@0 114 public function testCRUDFields() {
Chris@0 115 $this->manageFieldsPage();
Chris@0 116 $this->createField();
Chris@0 117 $this->updateField();
Chris@0 118 $this->addExistingField();
Chris@0 119 $this->cardinalitySettings();
Chris@0 120 $this->fieldListAdminPage();
Chris@0 121 $this->deleteField();
Chris@0 122 $this->addPersistentFieldStorage();
Chris@0 123 }
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Tests the manage fields page.
Chris@0 127 *
Chris@0 128 * @param string $type
Chris@0 129 * (optional) The name of a content type.
Chris@0 130 */
Chris@0 131 public function manageFieldsPage($type = '') {
Chris@0 132 $type = empty($type) ? $this->contentType : $type;
Chris@0 133 $this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
Chris@0 134 // Check all table columns.
Chris@0 135 $table_headers = [
Chris@0 136 t('Label'),
Chris@0 137 t('Machine name'),
Chris@0 138 t('Field type'),
Chris@0 139 t('Operations'),
Chris@0 140 ];
Chris@0 141 foreach ($table_headers as $table_header) {
Chris@0 142 // We check that the label appear in the table headings.
Chris@0 143 $this->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', ['%table_header' => $table_header]));
Chris@0 144 }
Chris@0 145
Chris@0 146 // Test the "Add field" action link.
Chris@0 147 $this->assertLink('Add field');
Chris@0 148
Chris@0 149 // Assert entity operations for all fields.
Chris@0 150 $number_of_links = 3;
Chris@0 151 $number_of_links_found = 0;
Chris@0 152 $operation_links = $this->xpath('//ul[@class = "dropbutton"]/li/a');
Chris@0 153 $url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body";
Chris@0 154
Chris@0 155 foreach ($operation_links as $link) {
Chris@0 156 switch ($link['title']) {
Chris@0 157 case 'Edit field settings.':
Chris@0 158 $this->assertIdentical($url, (string) $link['href']);
Chris@0 159 $number_of_links_found++;
Chris@0 160 break;
Chris@0 161 case 'Edit storage settings.':
Chris@0 162 $this->assertIdentical("$url/storage", (string) $link['href']);
Chris@0 163 $number_of_links_found++;
Chris@0 164 break;
Chris@0 165 case 'Delete field.':
Chris@0 166 $this->assertIdentical("$url/delete", (string) $link['href']);
Chris@0 167 $number_of_links_found++;
Chris@0 168 break;
Chris@0 169 }
Chris@0 170 }
Chris@0 171
Chris@0 172 $this->assertEqual($number_of_links, $number_of_links_found);
Chris@0 173 }
Chris@0 174
Chris@0 175 /**
Chris@0 176 * Tests adding a new field.
Chris@0 177 *
Chris@0 178 * @todo Assert properties can bet set in the form and read back in
Chris@0 179 * $field_storage and $fields.
Chris@0 180 */
Chris@0 181 public function createField() {
Chris@0 182 // Create a test field.
Chris@0 183 $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
Chris@0 184 }
Chris@0 185
Chris@0 186 /**
Chris@0 187 * Tests editing an existing field.
Chris@0 188 */
Chris@0 189 public function updateField() {
Chris@0 190 $field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
Chris@0 191 // Go to the field edit page.
Chris@0 192 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
Chris@0 193 $this->assertEscaped($this->fieldLabel);
Chris@0 194
Chris@0 195 // Populate the field settings with new settings.
Chris@0 196 $string = 'updated dummy test string';
Chris@0 197 $edit = [
Chris@0 198 'settings[test_field_storage_setting]' => $string,
Chris@0 199 ];
Chris@0 200 $this->drupalPostForm(NULL, $edit, t('Save field settings'));
Chris@0 201
Chris@0 202 // Go to the field edit page.
Chris@0 203 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
Chris@0 204 $edit = [
Chris@0 205 'settings[test_field_setting]' => $string,
Chris@0 206 ];
Chris@0 207 $this->assertText(t('Default value'), 'Default value heading is shown');
Chris@0 208 $this->drupalPostForm(NULL, $edit, t('Save settings'));
Chris@0 209
Chris@0 210 // Assert the field settings are correct.
Chris@0 211 $this->assertFieldSettings($this->contentType, $this->fieldName, $string);
Chris@0 212
Chris@0 213 // Assert redirection back to the "manage fields" page.
Chris@0 214 $this->assertUrl('admin/structure/types/manage/' . $this->contentType . '/fields');
Chris@0 215 }
Chris@0 216
Chris@0 217 /**
Chris@0 218 * Tests adding an existing field in another content type.
Chris@0 219 */
Chris@0 220 public function addExistingField() {
Chris@0 221 // Check "Re-use existing field" appears.
Chris@0 222 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
Chris@0 223 $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
Chris@0 224
Chris@0 225 // Check that fields of other entity types (here, the 'comment_body' field)
Chris@0 226 // do not show up in the "Re-use existing field" list.
Chris@0 227 $this->assertFalse($this->xpath('//select[@id="edit-existing-storage-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
Chris@0 228 // Validate the FALSE assertion above by also testing a valid one.
Chris@0 229 $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.');
Chris@0 230
Chris@0 231 // Add a new field based on an existing field.
Chris@0 232 $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $this->fieldLabel . '_2');
Chris@0 233 }
Chris@0 234
Chris@0 235 /**
Chris@0 236 * Tests the cardinality settings of a field.
Chris@0 237 *
Chris@0 238 * We do not test if the number can be submitted with anything else than a
Chris@0 239 * numeric value. That is tested already in FormTest::testNumber().
Chris@0 240 */
Chris@0 241 public function cardinalitySettings() {
Chris@0 242 $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/storage';
Chris@0 243
Chris@0 244 // Assert the cardinality other field cannot be empty when cardinality is
Chris@0 245 // set to 'number'.
Chris@0 246 $edit = [
Chris@0 247 'cardinality' => 'number',
Chris@0 248 'cardinality_number' => '',
Chris@0 249 ];
Chris@0 250 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 251 $this->assertText('Number of values is required.');
Chris@0 252
Chris@0 253 // Submit a custom number.
Chris@0 254 $edit = [
Chris@0 255 'cardinality' => 'number',
Chris@0 256 'cardinality_number' => 6,
Chris@0 257 ];
Chris@0 258 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 259 $this->assertText('Updated field Body field settings.');
Chris@0 260 $this->drupalGet($field_edit_path);
Chris@0 261 $this->assertFieldByXPath("//select[@name='cardinality']", 'number');
Chris@0 262 $this->assertFieldByXPath("//input[@name='cardinality_number']", 6);
Chris@0 263
Chris@0 264 // Check that tabs displayed.
Chris@0 265 $this->assertLink(t('Edit'));
Chris@0 266 $this->assertLinkByHref('admin/structure/types/manage/article/fields/node.article.body');
Chris@0 267 $this->assertLink(t('Field settings'));
Chris@0 268 $this->assertLinkByHref($field_edit_path);
Chris@0 269
Chris@0 270 // Add two entries in the body.
Chris@0 271 $edit = ['title[0][value]' => 'Cardinality', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2'];
Chris@0 272 $this->drupalPostForm('node/add/article', $edit, 'Save');
Chris@0 273
Chris@0 274 // Assert that you can't set the cardinality to a lower number than the
Chris@0 275 // highest delta of this field.
Chris@0 276 $edit = [
Chris@0 277 'cardinality' => 'number',
Chris@0 278 'cardinality_number' => 1,
Chris@0 279 ];
Chris@0 280 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 281 $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.');
Chris@0 282
Chris@0 283 // Create a second entity with three values.
Chris@0 284 $edit = ['title[0][value]' => 'Cardinality 3', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2', 'body[2][value]' => 'Body 3'];
Chris@0 285 $this->drupalPostForm('node/add/article', $edit, 'Save');
Chris@0 286
Chris@0 287 // Set to unlimited.
Chris@0 288 $edit = [
Chris@0 289 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
Chris@0 290 ];
Chris@0 291 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 292 $this->assertText('Updated field Body field settings.');
Chris@0 293 $this->drupalGet($field_edit_path);
Chris@0 294 $this->assertFieldByXPath("//select[@name='cardinality']", FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
Chris@0 295 $this->assertFieldByXPath("//input[@name='cardinality_number']", 1);
Chris@0 296
Chris@0 297 // Assert that you can't set the cardinality to a lower number then the
Chris@0 298 // highest delta of this field but can set it to the same.
Chris@0 299 $edit = [
Chris@0 300 'cardinality' => 'number',
Chris@0 301 'cardinality_number' => 1,
Chris@0 302 ];
Chris@0 303 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 304 $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.');
Chris@0 305
Chris@0 306 $edit = [
Chris@0 307 'cardinality' => 'number',
Chris@0 308 'cardinality_number' => 2,
Chris@0 309 ];
Chris@0 310 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 311 $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.');
Chris@0 312
Chris@0 313 $edit = [
Chris@0 314 'cardinality' => 'number',
Chris@0 315 'cardinality_number' => 3,
Chris@0 316 ];
Chris@0 317 $this->drupalPostForm($field_edit_path, $edit, t('Save field settings'));
Chris@0 318 }
Chris@0 319
Chris@0 320 /**
Chris@0 321 * Tests deleting a field from the field edit form.
Chris@0 322 */
Chris@0 323 protected function deleteField() {
Chris@0 324 // Delete the field.
Chris@0 325 $field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
Chris@0 326 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
Chris@0 327 $this->clickLink(t('Delete'));
Chris@0 328 $this->assertResponse(200);
Chris@0 329 }
Chris@0 330
Chris@0 331 /**
Chris@0 332 * Tests that persistent field storage appears in the field UI.
Chris@0 333 */
Chris@0 334 protected function addPersistentFieldStorage() {
Chris@0 335 $field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
Chris@0 336 // Persist the field storage even if there are no fields.
Chris@0 337 $field_storage->set('persist_with_no_fields', TRUE)->save();
Chris@0 338 // Delete all instances of the field.
Chris@0 339 foreach ($field_storage->getBundles() as $node_type) {
Chris@0 340 // Delete all the body field instances.
Chris@0 341 $this->drupalGet('admin/structure/types/manage/' . $node_type . '/fields/node.' . $node_type . '.' . $this->fieldName);
Chris@0 342 $this->clickLink(t('Delete'));
Chris@0 343 $this->drupalPostForm(NULL, [], t('Delete'));
Chris@0 344 }
Chris@0 345 // Check "Re-use existing field" appears.
Chris@0 346 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
Chris@0 347 $this->assertRaw(t('Re-use an existing field'), '"Re-use existing field" was found.');
Chris@0 348
Chris@0 349 // Ensure that we test with a label that contains HTML.
Chris@0 350 $label = $this->randomString(4) . '<br/>' . $this->randomString(4);
Chris@0 351 // Add a new field for the orphaned storage.
Chris@0 352 $this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $label);
Chris@0 353 }
Chris@0 354
Chris@0 355 /**
Chris@0 356 * Asserts field settings are as expected.
Chris@0 357 *
Chris@0 358 * @param $bundle
Chris@0 359 * The bundle name for the field.
Chris@0 360 * @param $field_name
Chris@0 361 * The field name for the field.
Chris@0 362 * @param $string
Chris@0 363 * The settings text.
Chris@0 364 * @param $entity_type
Chris@0 365 * The entity type for the field.
Chris@0 366 */
Chris@0 367 public function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
Chris@0 368 // Assert field storage settings.
Chris@0 369 $field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
Chris@0 370 $this->assertTrue($field_storage->getSetting('test_field_storage_setting') == $string, 'Field storage settings were found.');
Chris@0 371
Chris@0 372 // Assert field settings.
Chris@0 373 $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
Chris@0 374 $this->assertTrue($field->getSetting('test_field_setting') == $string, 'Field settings were found.');
Chris@0 375 }
Chris@0 376
Chris@0 377 /**
Chris@0 378 * Tests that the 'field_prefix' setting works on Field UI.
Chris@0 379 */
Chris@0 380 public function testFieldPrefix() {
Chris@0 381 // Change default field prefix.
Chris@0 382 $field_prefix = strtolower($this->randomMachineName(10));
Chris@0 383 $this->config('field_ui.settings')->set('field_prefix', $field_prefix)->save();
Chris@0 384
Chris@0 385 // Create a field input and label exceeding the new maxlength, which is 22.
Chris@0 386 $field_exceed_max_length_label = $this->randomString(23);
Chris@0 387 $field_exceed_max_length_input = $this->randomMachineName(23);
Chris@0 388
Chris@0 389 // Try to create the field.
Chris@0 390 $edit = [
Chris@0 391 'label' => $field_exceed_max_length_label,
Chris@0 392 'field_name' => $field_exceed_max_length_input,
Chris@0 393 ];
Chris@0 394 $this->drupalPostForm('admin/structure/types/manage/' . $this->contentType . '/fields/add-field', $edit, t('Save and continue'));
Chris@0 395 $this->assertText('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
Chris@0 396
Chris@0 397 // Create a valid field.
Chris@0 398 $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
Chris@0 399 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_prefix . $this->fieldNameInput);
Chris@0 400 $this->assertText(format_string('@label settings for @type', ['@label' => $this->fieldLabel, '@type' => $this->contentType]));
Chris@0 401 }
Chris@0 402
Chris@0 403 /**
Chris@0 404 * Tests that default value is correctly validated and saved.
Chris@0 405 */
Chris@0 406 public function testDefaultValue() {
Chris@0 407 // Create a test field storage and field.
Chris@0 408 $field_name = 'test';
Chris@0 409 FieldStorageConfig::create([
Chris@0 410 'field_name' => $field_name,
Chris@0 411 'entity_type' => 'node',
Chris@0 412 'type' => 'test_field'
Chris@0 413 ])->save();
Chris@0 414 $field = FieldConfig::create([
Chris@0 415 'field_name' => $field_name,
Chris@0 416 'entity_type' => 'node',
Chris@0 417 'bundle' => $this->contentType,
Chris@0 418 ]);
Chris@0 419 $field->save();
Chris@0 420
Chris@0 421 entity_get_form_display('node', $this->contentType, 'default')
Chris@0 422 ->setComponent($field_name)
Chris@0 423 ->save();
Chris@0 424
Chris@0 425 $admin_path = 'admin/structure/types/manage/' . $this->contentType . '/fields/' . $field->id();
Chris@0 426 $element_id = "edit-default-value-input-$field_name-0-value";
Chris@0 427 $element_name = "default_value_input[{$field_name}][0][value]";
Chris@0 428 $this->drupalGet($admin_path);
Chris@0 429 $this->assertFieldById($element_id, '', 'The default value widget was empty.');
Chris@0 430
Chris@0 431 // Check that invalid default values are rejected.
Chris@0 432 $edit = [$element_name => '-1'];
Chris@0 433 $this->drupalPostForm($admin_path, $edit, t('Save settings'));
Chris@0 434 $this->assertText("$field_name does not accept the value -1", 'Form validation failed.');
Chris@0 435
Chris@0 436 // Check that the default value is saved.
Chris@0 437 $edit = [$element_name => '1'];
Chris@0 438 $this->drupalPostForm($admin_path, $edit, t('Save settings'));
Chris@0 439 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Chris@0 440 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
Chris@0 441 $this->assertEqual($field->getDefaultValueLiteral(), [['value' => 1]], 'The default value was correctly saved.');
Chris@0 442
Chris@0 443 // Check that the default value shows up in the form
Chris@0 444 $this->drupalGet($admin_path);
Chris@0 445 $this->assertFieldById($element_id, '1', 'The default value widget was displayed with the correct value.');
Chris@0 446
Chris@0 447 // Check that the default value can be emptied.
Chris@0 448 $edit = [$element_name => ''];
Chris@0 449 $this->drupalPostForm(NULL, $edit, t('Save settings'));
Chris@0 450 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Chris@0 451 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
Chris@0 452 $this->assertEqual($field->getDefaultValueLiteral(), NULL, 'The default value was correctly saved.');
Chris@0 453
Chris@0 454 // Check that the default value can be empty when the field is marked as
Chris@0 455 // required and can store unlimited values.
Chris@0 456 $field_storage = FieldStorageConfig::loadByName('node', $field_name);
Chris@0 457 $field_storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
Chris@0 458 $field_storage->save();
Chris@0 459
Chris@0 460 $this->drupalGet($admin_path);
Chris@0 461 $edit = [
Chris@0 462 'required' => 1,
Chris@0 463 ];
Chris@0 464 $this->drupalPostForm(NULL, $edit, t('Save settings'));
Chris@0 465
Chris@0 466 $this->drupalGet($admin_path);
Chris@0 467 $this->drupalPostForm(NULL, [], t('Save settings'));
Chris@0 468 $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
Chris@0 469 $field = FieldConfig::loadByName('node', $this->contentType, $field_name);
Chris@0 470 $this->assertEqual($field->getDefaultValueLiteral(), NULL, 'The default value was correctly saved.');
Chris@0 471
Chris@0 472 // Check that the default widget is used when the field is hidden.
Chris@0 473 entity_get_form_display($field->getTargetEntityTypeId(), $field->getTargetBundle(), 'default')
Chris@0 474 ->removeComponent($field_name)->save();
Chris@0 475 $this->drupalGet($admin_path);
Chris@0 476 $this->assertFieldById($element_id, '', 'The default value widget was displayed when field is hidden.');
Chris@0 477 }
Chris@0 478
Chris@0 479 /**
Chris@0 480 * Tests that deletion removes field storages and fields as expected.
Chris@0 481 */
Chris@0 482 public function testDeleteField() {
Chris@0 483 // Create a new field.
Chris@0 484 $bundle_path1 = 'admin/structure/types/manage/' . $this->contentType;
Chris@0 485 $this->fieldUIAddNewField($bundle_path1, $this->fieldNameInput, $this->fieldLabel);
Chris@0 486
Chris@0 487 // Create an additional node type.
Chris@0 488 $type_name2 = strtolower($this->randomMachineName(8)) . '_test';
Chris@0 489 $type2 = $this->drupalCreateContentType(['name' => $type_name2, 'type' => $type_name2]);
Chris@0 490 $type_name2 = $type2->id();
Chris@0 491
Chris@0 492 // Add a field to the second node type.
Chris@0 493 $bundle_path2 = 'admin/structure/types/manage/' . $type_name2;
Chris@0 494 $this->fieldUIAddExistingField($bundle_path2, $this->fieldName, $this->fieldLabel);
Chris@0 495
Chris@0 496 // Delete the first field.
Chris@0 497 $this->fieldUIDeleteField($bundle_path1, "node.$this->contentType.$this->fieldName", $this->fieldLabel, $this->contentType);
Chris@0 498
Chris@0 499 // Check that the field was deleted.
Chris@0 500 $this->assertNull(FieldConfig::loadByName('node', $this->contentType, $this->fieldName), 'Field was deleted.');
Chris@0 501 // Check that the field storage was not deleted
Chris@0 502 $this->assertNotNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was not deleted.');
Chris@0 503
Chris@0 504 // Delete the second field.
Chris@0 505 $this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$this->fieldName", $this->fieldLabel, $type_name2);
Chris@0 506
Chris@0 507 // Check that the field was deleted.
Chris@0 508 $this->assertNull(FieldConfig::loadByName('node', $type_name2, $this->fieldName), 'Field was deleted.');
Chris@0 509 // Check that the field storage was deleted too.
Chris@0 510 $this->assertNull(FieldStorageConfig::loadByName('node', $this->fieldName), 'Field storage was deleted.');
Chris@0 511 }
Chris@0 512
Chris@0 513 /**
Chris@0 514 * Tests that Field UI respects disallowed field names.
Chris@0 515 */
Chris@0 516 public function testDisallowedFieldNames() {
Chris@0 517 // Reset the field prefix so we can test properly.
Chris@0 518 $this->config('field_ui.settings')->set('field_prefix', '')->save();
Chris@0 519
Chris@0 520 $label = 'Disallowed field';
Chris@0 521 $edit = [
Chris@0 522 'label' => $label,
Chris@0 523 'new_storage_type' => 'test_field',
Chris@0 524 ];
Chris@0 525
Chris@0 526 // Try with an entity key.
Chris@0 527 $edit['field_name'] = 'title';
Chris@0 528 $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
Chris@0 529 $this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
Chris@0 530 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
Chris@0 531
Chris@0 532 // Try with a base field.
Chris@0 533 $edit['field_name'] = 'sticky';
Chris@0 534 $bundle_path = 'admin/structure/types/manage/' . $this->contentType;
Chris@0 535 $this->drupalPostForm("$bundle_path/fields/add-field", $edit, t('Save and continue'));
Chris@0 536 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
Chris@0 537 }
Chris@0 538
Chris@0 539 /**
Chris@0 540 * Tests that Field UI respects locked fields.
Chris@0 541 */
Chris@0 542 public function testLockedField() {
Chris@0 543 // Create a locked field and attach it to a bundle. We need to do this
Chris@0 544 // programmatically as there's no way to create a locked field through UI.
Chris@0 545 $field_name = strtolower($this->randomMachineName(8));
Chris@0 546 $field_storage = FieldStorageConfig::create([
Chris@0 547 'field_name' => $field_name,
Chris@0 548 'entity_type' => 'node',
Chris@0 549 'type' => 'test_field',
Chris@0 550 'cardinality' => 1,
Chris@0 551 'locked' => TRUE
Chris@0 552 ]);
Chris@0 553 $field_storage->save();
Chris@0 554 FieldConfig::create([
Chris@0 555 'field_storage' => $field_storage,
Chris@0 556 'bundle' => $this->contentType,
Chris@0 557 ])->save();
Chris@0 558 entity_get_form_display('node', $this->contentType, 'default')
Chris@0 559 ->setComponent($field_name, [
Chris@0 560 'type' => 'test_field_widget',
Chris@0 561 ])
Chris@0 562 ->save();
Chris@0 563
Chris@0 564 // Check that the links for edit and delete are not present.
Chris@0 565 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
Chris@0 566 $locked = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
Chris@0 567 $this->assertTrue(in_array('Locked', $locked), 'Field is marked as Locked in the UI');
Chris@0 568 $edit_link = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
Chris@0 569 $this->assertFalse(in_array('edit', $edit_link), 'Edit option for locked field is not present the UI');
Chris@0 570 $delete_link = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
Chris@0 571 $this->assertFalse(in_array('delete', $delete_link), 'Delete option for locked field is not present the UI');
Chris@0 572 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_name . '/delete');
Chris@0 573 $this->assertResponse(403);
Chris@0 574 }
Chris@0 575
Chris@0 576 /**
Chris@0 577 * Tests that Field UI respects the 'no_ui' flag in the field type definition.
Chris@0 578 */
Chris@0 579 public function testHiddenFields() {
Chris@0 580 // Check that the field type is not available in the 'add new field' row.
Chris@0 581 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
Chris@0 582 $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.");
Chris@0 583 $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value="shape"]'), "The 'add new field' select shows a valid option.");
Chris@0 584
Chris@0 585 // Create a field storage and a field programmatically.
Chris@0 586 $field_name = 'hidden_test_field';
Chris@0 587 FieldStorageConfig::create([
Chris@0 588 'field_name' => $field_name,
Chris@0 589 'entity_type' => 'node',
Chris@0 590 'type' => $field_name,
Chris@0 591 ])->save();
Chris@0 592 $field = [
Chris@0 593 'field_name' => $field_name,
Chris@0 594 'bundle' => $this->contentType,
Chris@0 595 'entity_type' => 'node',
Chris@0 596 'label' => t('Hidden field'),
Chris@0 597 ];
Chris@0 598 FieldConfig::create($field)->save();
Chris@0 599 entity_get_form_display('node', $this->contentType, 'default')
Chris@0 600 ->setComponent($field_name)
Chris@0 601 ->save();
Chris@0 602 $this->assertTrue(FieldConfig::load('node.' . $this->contentType . '.' . $field_name), format_string('A field of the field storage %field was created programmatically.', ['%field' => $field_name]));
Chris@0 603
Chris@0 604 // Check that the newly added field appears on the 'Manage Fields'
Chris@0 605 // screen.
Chris@0 606 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
Chris@0 607 $this->assertFieldByXPath('//table[@id="field-overview"]//tr[@id="hidden-test-field"]//td[1]', $field['label'], 'Field was created and appears in the overview page.');
Chris@0 608
Chris@0 609 // Check that the field does not appear in the 're-use existing field' row
Chris@0 610 // on other bundles.
Chris@0 611 $this->drupalGet('admin/structure/types/manage/page/fields/add-field');
Chris@0 612 $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.");
Chris@0 613 $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.");
Chris@0 614
Chris@0 615 // Check that non-configurable fields are not available.
Chris@0 616 $field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
Chris@0 617 foreach ($field_types as $field_type => $definition) {
Chris@0 618 if (empty($definition['no_ui'])) {
Chris@0 619 $this->assertTrue($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', [':field_type' => $field_type]), SafeMarkup::format('Configurable field type @field_type is available.', ['@field_type' => $field_type]));
Chris@0 620 }
Chris@0 621 else {
Chris@0 622 $this->assertFalse($this->xpath('//select[@id="edit-new-storage-type"]//option[@value=:field_type]', [':field_type' => $field_type]), SafeMarkup::format('Non-configurable field type @field_type is not available.', ['@field_type' => $field_type]));
Chris@0 623 }
Chris@0 624 }
Chris@0 625 }
Chris@0 626
Chris@0 627 /**
Chris@0 628 * Tests that a duplicate field name is caught by validation.
Chris@0 629 */
Chris@0 630 public function testDuplicateFieldName() {
Chris@0 631 // field_tags already exists, so we're expecting an error when trying to
Chris@0 632 // create a new field with the same name.
Chris@0 633 $edit = [
Chris@0 634 'field_name' => 'tags',
Chris@0 635 'label' => $this->randomMachineName(),
Chris@0 636 'new_storage_type' => 'entity_reference',
Chris@0 637 ];
Chris@0 638 $url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
Chris@0 639 $this->drupalPostForm($url, $edit, t('Save and continue'));
Chris@0 640
Chris@0 641 $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
Chris@0 642 $this->assertUrl($url, [], 'Stayed on the same page.');
Chris@0 643 }
Chris@0 644
Chris@0 645 /**
Chris@0 646 * Tests that external URLs in the 'destinations' query parameter are blocked.
Chris@0 647 */
Chris@0 648 public function testExternalDestinations() {
Chris@0 649 $options = [
Chris@0 650 'query' => ['destinations' => ['http://example.com']],
Chris@0 651 ];
Chris@0 652 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.body/storage', [], 'Save field settings', $options);
Chris@0 653 // The external redirect should not fire.
Chris@0 654 $this->assertUrl('admin/structure/types/manage/article/fields/node.article.body/storage', $options);
Chris@0 655 $this->assertResponse(200);
Chris@0 656 $this->assertRaw('Attempt to update field <em class="placeholder">Body</em> failed: <em class="placeholder">The internal path component &#039;http://example.com&#039; is external. You are not allowed to specify an external URL together with internal:/.</em>.');
Chris@0 657 }
Chris@0 658
Chris@0 659 /**
Chris@0 660 * Tests that deletion removes field storages and fields as expected for a term.
Chris@0 661 */
Chris@0 662 public function testDeleteTaxonomyField() {
Chris@0 663 // Create a new field.
Chris@0 664 $bundle_path = 'admin/structure/taxonomy/manage/tags/overview';
Chris@0 665
Chris@0 666 $this->fieldUIAddNewField($bundle_path, $this->fieldNameInput, $this->fieldLabel);
Chris@0 667
Chris@0 668 // Delete the field.
Chris@0 669 $this->fieldUIDeleteField($bundle_path, "taxonomy_term.tags.$this->fieldName", $this->fieldLabel, 'Tags');
Chris@0 670
Chris@0 671 // Check that the field was deleted.
Chris@0 672 $this->assertNull(FieldConfig::loadByName('taxonomy_term', 'tags', $this->fieldName), 'Field was deleted.');
Chris@0 673 // Check that the field storage was deleted too.
Chris@0 674 $this->assertNull(FieldStorageConfig::loadByName('taxonomy_term', $this->fieldName), 'Field storage was deleted.');
Chris@0 675 }
Chris@0 676
Chris@0 677 /**
Chris@0 678 * Tests that help descriptions render valid HTML.
Chris@0 679 */
Chris@0 680 public function testHelpDescriptions() {
Chris@0 681 // Create an image field
Chris@0 682 FieldStorageConfig::create([
Chris@0 683 'field_name' => 'field_image',
Chris@0 684 'entity_type' => 'node',
Chris@0 685 'type' => 'image',
Chris@0 686 ])->save();
Chris@0 687
Chris@0 688 FieldConfig::create([
Chris@0 689 'field_name' => 'field_image',
Chris@0 690 'entity_type' => 'node',
Chris@0 691 'label' => 'Image',
Chris@0 692 'bundle' => 'article',
Chris@0 693 ])->save();
Chris@0 694
Chris@0 695 entity_get_form_display('node', 'article', 'default')->setComponent('field_image')->save();
Chris@0 696
Chris@0 697 $edit = [
Chris@0 698 'description' => '<strong>Test with an upload field.',
Chris@0 699 ];
Chris@0 700 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_image', $edit, t('Save settings'));
Chris@0 701
Chris@0 702 // Check that hook_field_widget_form_alter() does believe this is the
Chris@0 703 // default value form.
Chris@0 704 $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_tags');
Chris@0 705 $this->assertText('From hook_field_widget_form_alter(): Default form is true.', 'Default value form in hook_field_widget_form_alter().');
Chris@0 706
Chris@0 707 $edit = [
Chris@0 708 'description' => '<em>Test with a non upload field.',
Chris@0 709 ];
Chris@0 710 $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_tags', $edit, t('Save settings'));
Chris@0 711
Chris@0 712 $this->drupalGet('node/add/article');
Chris@0 713 $this->assertRaw('<strong>Test with an upload field.</strong>');
Chris@0 714 $this->assertRaw('<em>Test with a non upload field.</em>');
Chris@0 715 }
Chris@0 716
Chris@0 717 /**
Chris@0 718 * Tests that the field list administration page operates correctly.
Chris@0 719 */
Chris@0 720 public function fieldListAdminPage() {
Chris@0 721 $this->drupalGet('admin/reports/fields');
Chris@0 722 $this->assertText($this->fieldName, 'Field name is displayed in field list.');
Chris@0 723 $this->assertTrue($this->assertLinkByHref('admin/structure/types/manage/' . $this->contentType . '/fields'), 'Link to content type using field is displayed in field list.');
Chris@0 724 }
Chris@0 725
Chris@0 726 /**
Chris@0 727 * Tests the "preconfigured field" functionality.
Chris@0 728 *
Chris@0 729 * @see \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface
Chris@0 730 */
Chris@0 731 public function testPreconfiguredFields() {
Chris@0 732 $this->drupalGet('admin/structure/types/manage/article/fields/add-field');
Chris@0 733
Chris@0 734 // Check that the preconfigured field option exist alongside the regular
Chris@0 735 // field type option.
Chris@0 736 $this->assertOption('edit-new-storage-type', 'field_ui:test_field_with_preconfigured_options:custom_options');
Chris@0 737 $this->assertOption('edit-new-storage-type', 'test_field_with_preconfigured_options');
Chris@0 738
Chris@0 739 // Add a field with every possible preconfigured value.
Chris@0 740 $this->fieldUIAddNewField(NULL, 'test_custom_options', 'Test label', 'field_ui:test_field_with_preconfigured_options:custom_options');
Chris@0 741 $field_storage = FieldStorageConfig::loadByName('node', 'field_test_custom_options');
Chris@0 742 $this->assertEqual($field_storage->getCardinality(), FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
Chris@0 743 $this->assertEqual($field_storage->getSetting('test_field_storage_setting'), 'preconfigured_storage_setting');
Chris@0 744
Chris@0 745 $field = FieldConfig::loadByName('node', 'article', 'field_test_custom_options');
Chris@0 746 $this->assertTrue($field->isRequired());
Chris@0 747 $this->assertEqual($field->getSetting('test_field_setting'), 'preconfigured_field_setting');
Chris@0 748
Chris@0 749 $form_display = entity_get_form_display('node', 'article', 'default');
Chris@0 750 $this->assertEqual($form_display->getComponent('field_test_custom_options')['type'], 'test_field_widget_multiple');
Chris@0 751 $view_display = entity_get_display('node', 'article', 'default');
Chris@0 752 $this->assertEqual($view_display->getComponent('field_test_custom_options')['type'], 'field_test_multiple');
Chris@0 753 }
Chris@0 754
Chris@0 755 /**
Chris@0 756 * Tests the access to non-existent field URLs.
Chris@0 757 */
Chris@0 758 public function testNonExistentFieldUrls() {
Chris@0 759 $field_id = 'node.foo.bar';
Chris@0 760
Chris@0 761 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
Chris@0 762 $this->assertResponse(404);
Chris@0 763
Chris@0 764 $this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id . '/storage');
Chris@0 765 $this->assertResponse(404);
Chris@0 766 }
Chris@0 767
Chris@0 768 }