ManageFieldsTest.php 21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
<?php

/**
 * @file
 * Definition of Drupal\field_ui\Tests\ManageFieldsTest.
 */

namespace Drupal\field_ui\Tests;

/**
 * Tests the functionality of the 'Manage fields' screen.
 */
class ManageFieldsTest extends FieldUiTestBase {
  public static function getInfo() {
    return array(
      'name' => 'Manage fields',
      'description' => 'Test the Field UI "Manage fields" screen.',
      'group' => 'Field UI',
    );
  }

  function setUp() {
    parent::setUp();

    // Create random field name.
    $this->field_label = $this->randomName(8);
    $this->field_name_input =  strtolower($this->randomName(8));
    $this->field_name = 'field_'. $this->field_name_input;

    // Create Basic page and Article node types.
    $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
    $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));

    // Create a vocabulary named "Tags".
    $vocabulary = entity_create('taxonomy_vocabulary', array(
      'name' => 'Tags',
37
      'vid' => 'tags',
38 39
      'langcode' => LANGUAGE_NOT_SPECIFIED,
    ));
40
    $vocabulary->save();
41 42

    $field = array(
43
      'field_name' => 'field_' . $vocabulary->id(),
44 45 46 47 48
      'type' => 'taxonomy_term_reference',
    );
    field_create_field($field);

    $instance = array(
49
      'field_name' => 'field_' . $vocabulary->id(),
50 51 52 53 54
      'entity_type' => 'node',
      'label' => 'Tags',
      'bundle' => 'article',
    );
    field_create_instance($instance);
55 56 57 58

    entity_get_form_display('node', 'article', 'default')
      ->setComponent('field_' . $vocabulary->id())
      ->save();
59 60 61 62 63 64 65 66 67 68 69 70 71
  }

  /**
   * 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.
   */
  function testCRUDFields() {
    $this->manageFieldsPage();
    $this->createField();
    $this->updateField();
    $this->addExistingField();
72
    $this->cardinalitySettings();
73 74 75 76
  }

  /**
   * Tests the manage fields page.
77 78 79
   *
   * @param string $type
   *   (optional) The name of a content type.
80
   */
81 82 83
  function manageFieldsPage($type = '') {
    $type = empty($type) ? $this->type : $type;
    $this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
84 85 86 87 88 89 90 91 92 93
    // Check all table columns.
    $table_headers = array(
      t('Label'),
      t('Machine name'),
      t('Field type'),
      t('Widget'),
      t('Operations'),
    );
    foreach ($table_headers as $table_header) {
      // We check that the label appear in the table headings.
94
      $this->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', array('%table_header' => $table_header)));
95 96
    }

97
    // "Add new field" and "Re-use existing field" aren't a table heading so just
98
    // test the text.
99
    foreach (array('Add new field', 'Re-use existing field') as $element) {
100
      $this->assertText($element, format_string('"@element" was found.', array('@element' => $element)));
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
    }
  }

  /**
   * Tests adding a new field.
   *
   * @todo Assert properties can bet set in the form and read back in $field and
   * $instances.
   */
  function createField() {
    // Create a test field.
    $edit = array(
      'fields[_add_new_field][label]' => $this->field_label,
      'fields[_add_new_field][field_name]' => $this->field_name_input,
    );
    $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->type, $edit);

118
    // Assert the field appears in the "re-use existing field" section for
119 120
    // different entity types; e.g. if a field was added in a node entity, it
    // should also appear in the 'taxonomy term' entity.
121
    $vocabulary = taxonomy_vocabulary_load('tags');
122
    $this->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/fields');
123
    $this->assertTrue($this->xpath('//select[@name="fields[_add_existing_field][field_name]"]//option[@value="' . $this->field_name . '"]'), 'Existing field was found in taxonomy term fields.');
124 125 126 127 128 129
  }

  /**
   * Tests editing an existing field.
   */
  function updateField() {
130
    $instance_id = 'node.' . $this->type . '.' . $this->field_name;
131
    // Go to the field edit page.
132
    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/' . $instance_id . '/field');
133 134 135 136 137

    // Populate the field settings with new settings.
    $string = 'updated dummy test string';
    $edit = array(
      'field[settings][test_field_setting]' => $string,
138 139 140 141
    );
    $this->drupalPost(NULL, $edit, t('Save field settings'));

    // Go to the field instance edit page.
142
    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/' . $instance_id);
143
    $edit = array(
144 145 146 147 148 149 150 151 152
      'instance[settings][test_instance_setting]' => $string,
      'instance[widget][settings][test_widget_setting]' => $string,
    );
    $this->drupalPost(NULL, $edit, t('Save settings'));

    // Assert the field settings are correct.
    $this->assertFieldSettings($this->type, $this->field_name, $string);

    // Assert redirection back to the "manage fields" page.
153
    $this->assertUrl('admin/structure/types/manage/' . $this->type . '/fields');
154 155 156 157 158 159
  }

  /**
   * Tests adding an existing field in another content type.
   */
  function addExistingField() {
160
    // Check "Re-use existing field" appears.
161
    $this->drupalGet('admin/structure/types/manage/page/fields');
162
    $this->assertRaw(t('Re-use existing field'), '"Re-use existing field" was found.');
163 164 165 166

    // Check that the list of options respects entity type restrictions on
    // fields. The 'comment' field is restricted to the 'comment' entity type
    // and should not appear in the list.
167
    $this->assertFalse($this->xpath('//select[@id="edit-add-existing-field-field-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');
168 169 170 171 172 173 174 175 176

    // Add a new field based on an existing field.
    $edit = array(
      'fields[_add_existing_field][label]' => $this->field_label . '_2',
      'fields[_add_existing_field][field_name]' => $this->field_name,
    );
    $this->fieldUIAddExistingField("admin/structure/types/manage/page", $edit);
  }

177 178 179 180 181 182 183
  /**
   * 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().
   */
  function cardinalitySettings() {
184
    $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/field';
185 186

    // Assert the cardinality other field cannot be empty when cardinality is
187
    // set to 'number'.
188
    $edit = array(
189 190
      'field[cardinality]' => 'number',
      'field[cardinality_number]' => '',
191
    );
192
    $this->drupalPost($field_edit_path, $edit, t('Save field settings'));
193 194
    $this->assertText('Number of values is required.');

195
    // Submit a custom number.
196
    $edit = array(
197 198
      'field[cardinality]' => 'number',
      'field[cardinality_number]' => 6,
199
    );
200 201
    $this->drupalPost($field_edit_path, $edit, t('Save field settings'));
    $this->assertText('Updated field Body field settings.');
202
    $this->drupalGet($field_edit_path);
203 204
    $this->assertFieldByXPath("//select[@name='field[cardinality]']", 'number');
    $this->assertFieldByXPath("//input[@name='field[cardinality_number]']", 6);
205

206
    // Set to unlimited.
207
    $edit = array(
208
      'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED,
209
    );
210 211
    $this->drupalPost($field_edit_path, $edit, t('Save field settings'));
    $this->assertText('Updated field Body field settings.');
212
    $this->drupalGet($field_edit_path);
213 214
    $this->assertFieldByXPath("//select[@name='field[cardinality]']", FIELD_CARDINALITY_UNLIMITED);
    $this->assertFieldByXPath("//input[@name='field[cardinality_number]']", 1);
215 216
  }

217 218 219 220 221 222 223 224 225 226 227 228 229 230
  /**
   * Asserts field settings are as expected.
   *
   * @param $bundle
   *   The bundle name for the instance.
   * @param $field_name
   *   The field name for the instance.
   * @param $string
   *   The settings text.
   * @param $entity_type
   *   The entity type for the instance.
   */
  function assertFieldSettings($bundle, $field_name, $string = 'dummy test string', $entity_type = 'node') {
    // Reset the fields info.
231
    field_info_cache_clear();
232 233
    // Assert field settings.
    $field = field_info_field($field_name);
234
    $this->assertTrue($field['settings']['test_field_setting'] == $string, 'Field settings were found.');
235 236 237

    // Assert instance and widget settings.
    $instance = field_info_instance($entity_type, $field_name, $bundle);
238
    $this->assertTrue($instance['settings']['test_instance_setting'] == $string, 'Field instance settings were found.');
239 240 241 242

    // Assert widget settings.
    $widget_configuration = entity_get_form_display($entity_type, $bundle, 'default')->getComponent($field_name);
    $this->assertTrue($widget_configuration['settings']['test_widget_setting'] == $string, 'Field widget settings were found.');
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
  }

  /**
   * Tests that default value is correctly validated and saved.
   */
  function testDefaultValue() {
    // Create a test field and instance.
    $field_name = 'test';
    $field = array(
      'field_name' => $field_name,
      'type' => 'test_field'
    );
    field_create_field($field);
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'node',
      'bundle' => $this->type,
    );
261
    $instance = field_create_instance($instance);
262

263 264 265 266
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field_name)
      ->save();

267
    $langcode = LANGUAGE_NOT_SPECIFIED;
268
    $admin_path = 'admin/structure/types/manage/' . $this->type . '/fields/' . $instance->id();
269 270 271
    $element_id = "edit-$field_name-$langcode-0-value";
    $element_name = "{$field_name}[$langcode][0][value]";
    $this->drupalGet($admin_path);
272
    $this->assertFieldById($element_id, '', 'The default value widget was empty.');
273 274 275 276

    // Check that invalid default values are rejected.
    $edit = array($element_name => '-1');
    $this->drupalPost($admin_path, $edit, t('Save settings'));
277
    $this->assertText("$field_name does not accept the value -1", 'Form vaildation failed.');
278 279 280 281

    // Check that the default value is saved.
    $edit = array($element_name => '1');
    $this->drupalPost($admin_path, $edit, t('Save settings'));
282
    $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
283
    field_info_cache_clear();
284
    $instance = field_info_instance('node', $field_name, $this->type);
285
    $this->assertEqual($instance['default_value'], array(array('value' => 1)), 'The default value was correctly saved.');
286 287 288

    // Check that the default value shows up in the form
    $this->drupalGet($admin_path);
289
    $this->assertFieldById($element_id, '1', 'The default value widget was displayed with the correct value.');
290 291 292 293

    // Check that the default value can be emptied.
    $edit = array($element_name => '');
    $this->drupalPost(NULL, $edit, t('Save settings'));
294
    $this->assertText("Saved $field_name configuration", 'The form was successfully submitted.');
295 296
    field_info_cache_clear();
    $instance = field_info_instance('node', $field_name, $this->type);
297
    $this->assertEqual($instance['default_value'], NULL, 'The default value was correctly saved.');
298 299

    // Change the widget to TestFieldWidgetNoDefault.
300 301 302 303 304
    entity_get_form_display($instance['entity_type'], $instance['bundle'], 'default')
      ->setComponent($field_name, array(
        'type' => 'test_field_widget_no_default',
      ))
      ->save();
305 306 307

    $this->drupalGet($admin_path);
    $this->assertNoFieldById($element_id, '', t('No default value was possible for widget that disables default value.'));
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
  }

  /**
   * Tests that deletion removes fields and instances as expected.
   */
  function testDeleteField() {
    // Create a new field.
    $bundle_path1 = 'admin/structure/types/manage/' . $this->type;
    $edit1 = array(
      'fields[_add_new_field][label]' => $this->field_label,
      'fields[_add_new_field][field_name]' => $this->field_name_input,
    );
    $this->fieldUIAddNewField($bundle_path1, $edit1);

    // Create an additional node type.
    $type_name2 = strtolower($this->randomName(8)) . '_test';
    $type2 = $this->drupalCreateContentType(array('name' => $type_name2, 'type' => $type_name2));
    $type_name2 = $type2->type;

    // Add an instance to the second node type.
    $bundle_path2 = 'admin/structure/types/manage/' . $type_name2;
    $edit2 = array(
      'fields[_add_existing_field][label]' => $this->field_label,
      'fields[_add_existing_field][field_name]' => $this->field_name,
    );
    $this->fieldUIAddExistingField($bundle_path2, $edit2);

    // Delete the first instance.
336
    $this->fieldUIDeleteField($bundle_path1, "node.$this->type.$this->field_name", $this->field_label, $this->type);
337 338

    // Reset the fields info.
339
    field_info_cache_clear();
340
    // Check that the field instance was deleted.
341
    $this->assertNull(field_info_instance('node', $this->field_name, $this->type), 'Field instance was deleted.');
342
    // Check that the field was not deleted
343
    $this->assertNotNull(field_info_field($this->field_name), 'Field was not deleted.');
344 345

    // Delete the second instance.
346
    $this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$this->field_name", $this->field_label, $type_name2);
347 348

    // Reset the fields info.
349
    field_info_cache_clear();
350
    // Check that the field instance was deleted.
351
    $this->assertNull(field_info_instance('node', $this->field_name, $type_name2), 'Field instance was deleted.');
352
    // Check that the field was deleted too.
353
    $this->assertNull(field_info_field($this->field_name), 'Field was deleted.');
354 355
  }

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
  /**
   * Tests that Field UI respects locked fields.
   */
  function testLockedField() {
    // Create a locked field and attach it to a bundle. We need to do this
    // programatically as there's no way to create a locked field through UI.
    $field = entity_create('field_entity', array(
      'field_name' => strtolower($this->randomName(8)),
      'type' => 'test_field',
      'cardinality' => 1,
      'locked' => TRUE
    ));
    $field->save();
    entity_create('field_instance', array(
      'field_uuid' => $field->uuid,
      'entity_type' => 'node',
      'bundle' => $this->type,
    ))->save();
374 375 376 377 378
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field->id, array(
        'type' => 'test_field_widget',
      ))
      ->save();
379 380 381 382 383 384 385 386 387 388 389

    // Check that the links for edit and delete are not present.
    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields');
    $locked = $this->xpath('//tr[@id=:field_name]/td[7]', array(':field_name' => $field->id()));
    $this->assertTrue(in_array('Locked', $locked), 'Field is marked as Locked in the UI');
    $edit_link = $this->xpath('//tr[@id=:field_name]/td[7]', array(':field_name' => $field->id()));
    $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[8]', array(':field_name' => $field->id()));
    $this->assertFalse(in_array('delete', $delete_link), 'Delete option for locked field is not present the UI');
  }

390 391 392 393 394 395 396 397
  /**
   * Tests that Field UI respects the 'no_ui' option in hook_field_info().
   */
  function testHiddenFields() {
    $bundle_path = 'admin/structure/types/manage/' . $this->type . '/fields/';

    // Check that the field type is not available in the 'add new field' row.
    $this->drupalGet($bundle_path);
398
    $this->assertFalse($this->xpath('//select[@id="edit-add-new-field-type"]//option[@value="hidden_test_field"]'), "The 'add new field' select respects field types 'no_ui' property.");
399 400 401 402 403 404 405 406 407 408 409

    // Create a field and an instance programmatically.
    $field_name = 'hidden_test_field';
    field_create_field(array('field_name' => $field_name, 'type' => $field_name));
    $instance = array(
      'field_name' => $field_name,
      'bundle' => $this->type,
      'entity_type' => 'node',
      'label' => t('Hidden field'),
    );
    field_create_instance($instance);
410 411 412
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field_name)
      ->save();
413
    $this->assertTrue(field_read_instance('node', $field_name, $this->type), format_string('An instance of the field %field was created programmatically.', array('%field' => $field_name)));
414 415 416 417

    // Check that the newly added instance appears on the 'Manage Fields'
    // screen.
    $this->drupalGet($bundle_path);
418
    $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $instance['label'], 'Field was created and appears in the overview page.');
419

420
    // Check that the instance does not appear in the 're-use existing field' row
421 422 423
    // on other bundles.
    $bundle_path = 'admin/structure/types/manage/article/fields/';
    $this->drupalGet($bundle_path);
424
    $this->assertFalse($this->xpath('//select[@id="edit-add-existing-field-field-name"]//option[@value=:field_name]', array(':field_name' => $field_name)), "The 're-use existing field' select respects field types 'no_ui' property.");
425 426 427 428 429 430 431 432 433

    // Remove the form display component to check the fallback label.
    entity_get_form_display('node', $this->type, 'default')
      ->removeComponent($field_name)
      ->save();

    $this->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/');
    $this->assertLinkByHref(url('admin/structure/types/manage/' . $this->type . '/fields/node.' . $this->type . '.'  . $field_name . '/widget-type'));
    $this->assertLink('- Hidden -');
434 435 436 437 438 439 440 441 442 443 444 445
  }

  /**
   * Tests renaming a bundle.
   */
  function testRenameBundle() {
    $type2 = strtolower($this->randomName(8)) . '_test';

    $options = array(
      'type' => $type2,
    );
    $this->drupalPost('admin/structure/types/manage/' . $this->type, $options, t('Save content type'));
446
    $this->manageFieldsPage($type2);
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
  }

  /**
   * Tests that a duplicate field name is caught by validation.
   */
  function testDuplicateFieldName() {
    // field_tags already exists, so we're expecting an error when trying to
    // create a new field with the same name.
    $edit = array(
      'fields[_add_new_field][field_name]' => 'tags',
      'fields[_add_new_field][label]' => $this->randomName(),
      'fields[_add_new_field][type]' => 'taxonomy_term_reference',
      'fields[_add_new_field][widget_type]' => 'options_select',
    );
    $url = 'admin/structure/types/manage/' . $this->type . '/fields';
    $this->drupalPost($url, $edit, t('Save'));

    $this->assertText(t('The machine-readable name is already in use. It must be unique.'));
    $this->assertUrl($url, array(), 'Stayed on the same page.');
  }
467 468 469 470 471 472

  /**
   * Tests changing the widget used by a field.
   */
  function testWidgetChange() {
    $url_fields = 'admin/structure/types/manage/article/fields';
473
    $url_tags_widget = $url_fields . '/node.article.field_tags/widget-type';
474 475 476

    // Check that the field_tags field currently uses the 'options_select'
    // widget.
477 478
    $entity_form_display = entity_get_form_display('node', 'article', 'default')->getComponent('field_tags');
    $this->assertEqual($entity_form_display['type'], 'options_select');
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501

    // Check that the "Manage fields" page shows the correct widget type.
    $this->drupalGet($url_fields);
    $link = current($this->xpath('//a[contains(@href, :href)]', array(':href' => $url_tags_widget)));
    $this->assertEqual((string) $link, 'Select list');

    // Go to the 'Widget type' form and check that the correct widget is
    // selected.
    $this->drupalGet($url_tags_widget);
    $this->assertFieldByXPath("//select[@name='widget_type']", 'options_select');

    // Change the widget type.
    $edit = array(
      'widget_type' => 'options_buttons',
    );
    $this->drupalPost(NULL, $edit, t('Continue'));

    // Check that the "Manage fields" page shows the correct widget type.
    $link = current($this->xpath('//a[contains(@href, :href)]', array(':href' => $url_tags_widget)));
    $this->assertEqual((string) $link, 'Check boxes/radio buttons');

    // Check that the field uses the newly set widget.
    field_cache_clear();
502 503
    $widget_configuration = entity_get_form_display('node', 'article', 'default')->getComponent('field_tags');
    $this->assertEqual($widget_configuration['type'], 'options_buttons');
504 505 506 507 508 509 510

    // Go to the 'Widget type' form and check that the correct widget is
    // selected.
    $this->drupalGet($url_tags_widget);
    $this->assertFieldByXPath("//select[@name='widget_type']", 'options_buttons');
  }

511 512 513 514 515
  /**
   * Tests that deletion removes fields and instances as expected for a term.
   */
  function testDeleteTaxonomyField() {
    // Create a new field.
516
    $bundle_path = 'admin/structure/taxonomy/manage/tags';
517 518 519 520 521 522 523
    $edit1 = array(
      'fields[_add_new_field][label]' => $this->field_label,
      'fields[_add_new_field][field_name]' => $this->field_name_input,
    );
    $this->fieldUIAddNewField($bundle_path, $edit1);

    // Delete the field.
524
    $this->fieldUIDeleteField($bundle_path, "taxonomy_term.tags.$this->field_name", $this->field_label, 'Tags');
525 526 527 528 529 530 531 532 533

    // Reset the fields info.
    field_info_cache_clear();
    // Check that the field instance was deleted.
    $this->assertNull(field_info_instance('taxonomy_term', $this->field_name, 'tags'), 'Field instance was deleted.');
    // Check that the field was deleted too.
    $this->assertNull(field_info_field($this->field_name), 'Field was deleted.');
  }

534
}