ManageFieldsTest.php

Contains \Drupal\field_ui\Tests\ManageFieldsTest.

Namespace

Drupal\field_ui\Tests

File

drupal/core/modules/field_ui/lib/Drupal/field_ui/Tests/ManageFieldsTest.php
View source
<?php

/**
 * @file
 * Contains \Drupal\field_ui\Tests\ManageFieldsTest.
 */
namespace Drupal\field_ui\Tests;

use Drupal\Core\Language\Language;

/**
 * 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',
      'vid' => 'tags',
      'langcode' => Language::LANGCODE_NOT_SPECIFIED,
    ));
    $vocabulary
      ->save();
    $field = array(
      'field_name' => 'field_' . $vocabulary
        ->id(),
      'type' => 'taxonomy_term_reference',
    );
    field_create_field($field);
    $instance = array(
      'field_name' => 'field_' . $vocabulary
        ->id(),
      'entity_type' => 'node',
      'label' => 'Tags',
      'bundle' => 'article',
    );
    field_create_instance($instance);
    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.
   */
  function testCRUDFields() {
    $this
      ->manageFieldsPage();
    $this
      ->createField();
    $this
      ->updateField();
    $this
      ->addExistingField();
    $this
      ->cardinalitySettings();
  }

  /**
   * Tests the manage fields page.
   *
   * @param string $type
   *   (optional) The name of a content type.
   */
  function manageFieldsPage($type = '') {
    $type = empty($type) ? $this->type : $type;
    $this
      ->drupalGet('admin/structure/types/manage/' . $type . '/fields');

    // 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.
      $this
        ->assertRaw($table_header . '</th>', format_string('%table_header table header was found.', array(
        '%table_header' => $table_header,
      )));
    }

    // "Add new field" and "Re-use existing field" aren't a table heading so just
    // test the text.
    foreach (array(
      'Add new field',
      'Re-use existing field',
    ) as $element) {
      $this
        ->assertText($element, format_string('"@element" was found.', array(
        '@element' => $element,
      )));
    }
  }

  /**
   * 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);

    // Assert the field appears in the "re-use existing field" section for
    // different entity types; e.g. if a field was added in a node entity, it
    // should also appear in the 'taxonomy term' entity.
    $vocabulary = taxonomy_vocabulary_load('tags');
    $this
      ->drupalGet('admin/structure/taxonomy/manage/' . $vocabulary
      ->id() . '/fields');
    $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.');
  }

  /**
   * Tests editing an existing field.
   */
  function updateField() {
    $instance_id = 'node.' . $this->type . '.' . $this->field_name;

    // Go to the field edit page.
    $this
      ->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/' . $instance_id . '/field');

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

    // Go to the field instance edit page.
    $this
      ->drupalGet('admin/structure/types/manage/' . $this->type . '/fields/' . $instance_id);
    $edit = array(
      '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.
    $this
      ->assertUrl('admin/structure/types/manage/' . $this->type . '/fields');
  }

  /**
   * Tests adding an existing field in another content type.
   */
  function addExistingField() {

    // Check "Re-use existing field" appears.
    $this
      ->drupalGet('admin/structure/types/manage/page/fields');
    $this
      ->assertRaw(t('Re-use existing field'), '"Re-use existing field" was found.');

    // 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.
    $this
      ->assertFalse($this
      ->xpath('//select[@id="edit-add-existing-field-field-name"]//option[@value="comment"]'), 'The list of options respects entity type restrictions.');

    // 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);
  }

  /**
   * 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() {
    $field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body/field';

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

    // Submit a custom number.
    $edit = array(
      'field[cardinality]' => 'number',
      'field[cardinality_number]' => 6,
    );
    $this
      ->drupalPost($field_edit_path, $edit, t('Save field settings'));
    $this
      ->assertText('Updated field Body field settings.');
    $this
      ->drupalGet($field_edit_path);
    $this
      ->assertFieldByXPath("//select[@name='field[cardinality]']", 'number');
    $this
      ->assertFieldByXPath("//input[@name='field[cardinality_number]']", 6);

    // Set to unlimited.
    $edit = array(
      'field[cardinality]' => FIELD_CARDINALITY_UNLIMITED,
    );
    $this
      ->drupalPost($field_edit_path, $edit, t('Save field settings'));
    $this
      ->assertText('Updated field Body field settings.');
    $this
      ->drupalGet($field_edit_path);
    $this
      ->assertFieldByXPath("//select[@name='field[cardinality]']", FIELD_CARDINALITY_UNLIMITED);
    $this
      ->assertFieldByXPath("//input[@name='field[cardinality_number]']", 1);
  }

  /**
   * 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.
    field_info_cache_clear();

    // Assert field settings.
    $field = field_info_field($field_name);
    $this
      ->assertTrue($field['settings']['test_field_setting'] == $string, 'Field settings were found.');

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

    // 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.');
  }

  /**
   * 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,
    );
    $instance = field_create_instance($instance);
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field_name)
      ->save();
    $langcode = Language::LANGCODE_NOT_SPECIFIED;
    $admin_path = 'admin/structure/types/manage/' . $this->type . '/fields/' . $instance
      ->id();
    $element_id = "edit-{$field_name}-{$langcode}-0-value";
    $element_name = "{$field_name}[{$langcode}][0][value]";
    $this
      ->drupalGet($admin_path);
    $this
      ->assertFieldById($element_id, '', 'The default value widget was empty.');

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

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

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

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

    // Change the widget to TestFieldWidgetNoDefault.
    entity_get_form_display($instance['entity_type'], $instance['bundle'], 'default')
      ->setComponent($field_name, array(
      'type' => 'test_field_widget_no_default',
    ))
      ->save();
    $this
      ->drupalGet($admin_path);
    $this
      ->assertNoFieldById($element_id, '', t('No default value was possible for widget that disables default value.'));
  }

  /**
   * 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.
    $this
      ->fieldUIDeleteField($bundle_path1, "node.{$this->type}.{$this->field_name}", $this->field_label, $this->type);

    // Reset the fields info.
    field_info_cache_clear();

    // Check that the field instance was deleted.
    $this
      ->assertNull(field_info_instance('node', $this->field_name, $this->type), 'Field instance was deleted.');

    // Check that the field was not deleted
    $this
      ->assertNotNull(field_info_field($this->field_name), 'Field was not deleted.');

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

    // Reset the fields info.
    field_info_cache_clear();

    // Check that the field instance was deleted.
    $this
      ->assertNull(field_info_instance('node', $this->field_name, $type_name2), 'Field instance was deleted.');

    // Check that the field was deleted too.
    $this
      ->assertNull(field_info_field($this->field_name), 'Field was deleted.');
  }

  /**
   * 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();
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field->id, array(
      'type' => 'test_field_widget',
    ))
      ->save();

    // 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');
  }

  /**
   * 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);
    $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.");

    // 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);
    entity_get_form_display('node', $this->type, 'default')
      ->setComponent($field_name)
      ->save();
    $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,
    )));

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

    // Check that the instance does not appear in the 're-use existing field' row
    // on other bundles.
    $bundle_path = 'admin/structure/types/manage/article/fields/';
    $this
      ->drupalGet($bundle_path);
    $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.");

    // 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 -');
  }

  /**
   * 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'));
    $this
      ->manageFieldsPage($type2);
  }

  /**
   * 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.');
  }

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

    // Check that the field_tags field currently uses the 'options_select'
    // widget.
    $entity_form_display = entity_get_form_display('node', 'article', 'default')
      ->getComponent('field_tags');
    $this
      ->assertEqual($entity_form_display['type'], 'options_select');

    // 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();
    $widget_configuration = entity_get_form_display('node', 'article', 'default')
      ->getComponent('field_tags');
    $this
      ->assertEqual($widget_configuration['type'], 'options_buttons');

    // 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');
  }

  /**
   * Tests that deletion removes fields and instances as expected for a term.
   */
  function testDeleteTaxonomyField() {

    // Create a new field.
    $bundle_path = 'admin/structure/taxonomy/manage/tags';
    $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.
    $this
      ->fieldUIDeleteField($bundle_path, "taxonomy_term.tags.{$this->field_name}", $this->field_label, 'Tags');

    // 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.');
  }

  /**
   * Tests that help descriptions render valid HTML.
   */
  function testHelpDescriptions() {

    // Create an image field
    entity_create('field_entity', array(
      'field_name' => 'field_image',
      'type' => 'image',
    ))
      ->save();
    entity_create('field_instance', array(
      'field_name' => 'field_image',
      'entity_type' => 'node',
      'label' => 'Image',
      'bundle' => 'article',
    ))
      ->save();
    entity_get_form_display('node', 'article', 'default')
      ->setComponent('field_image')
      ->save();
    $edit = array(
      'instance[description]' => '<strong>Test with an upload field.',
    );
    $this
      ->drupalPost('admin/structure/types/manage/article/fields/node.article.field_image', $edit, t('Save settings'));
    $edit = array(
      'instance[description]' => '<em>Test with a non upload field.',
    );
    $this
      ->drupalPost('admin/structure/types/manage/article/fields/node.article.field_tags', $edit, t('Save settings'));
    $this
      ->drupalGet('node/add/article');
    $this
      ->assertRaw('<strong>Test with an upload field.</strong>');
    $this
      ->assertRaw('<em>Test with a non upload field.</em>');
  }

}

Classes

Namesort descending Description
ManageFieldsTest Tests the functionality of the 'Manage fields' screen.