drupalCreateUser(array('access content', 'administer content types', 'administer taxonomy', 'administer fields')); $this->drupalLogin($admin_user); // Create content type, with underscores. $type_name = strtolower($this->randomName(8)) . '_test'; $type = $this->drupalCreateContentType(array('name' => $type_name, 'type' => $type_name)); $this->type = $type->type; // Store a valid URL name, with hyphens instead of underscores. $this->hyphen_type = str_replace('_', '-', $this->type); } /** * Creates a new field through the Field UI. * * @param $bundle_path * Admin path of the bundle that the new field is to be attached to. * @param $initial_edit * $edit parameter for drupalPost() on the first step ('Manage fields' * screen). * @param $field_edit * $edit parameter for drupalPost() on the second step ('Field settings' * form). * @param $instance_edit * $edit parameter for drupalPost() on the third step ('Instance settings' * form). */ function fieldUIAddNewField($bundle_path, $initial_edit, $field_edit = array(), $instance_edit = array()) { // Use 'test_field' field type by default. $initial_edit += array( 'fields[_add_new_field][type]' => 'test_field', 'fields[_add_new_field][widget_type]' => 'test_field_widget', ); $label = $initial_edit['fields[_add_new_field][label]']; $field_name = $initial_edit['fields[_add_new_field][field_name]']; // First step : 'Add new field' on the 'Manage fields' page. $this->drupalPost("$bundle_path/fields", $initial_edit, t('Save')); $this->assertRaw(t('These settings apply to the %label field everywhere it is used.', array('%label' => $label)), 'Field settings page was displayed.'); // Second step : 'Field settings' form. $this->drupalPost(NULL, $field_edit, t('Save field settings')); $this->assertRaw(t('Updated field %label field settings.', array('%label' => $label)), 'Redirected to instance and widget settings page.'); // Third step : 'Instance settings' form. $this->drupalPost(NULL, $instance_edit, t('Save settings')); $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), 'Redirected to "Manage fields" page.'); // Check that the field appears in the overview form. $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $label, 'Field was created and appears in the overview page.'); } /** * Adds an existing field through the Field UI. * * @param $bundle_path * Admin path of the bundle that the field is to be attached to. * @param $initial_edit * $edit parameter for drupalPost() on the first step ('Manage fields' * screen). * @param $instance_edit * $edit parameter for drupalPost() on the second step ('Instance settings' * form). */ function fieldUIAddExistingField($bundle_path, $initial_edit, $instance_edit = array()) { // Use 'test_field_widget' by default. $initial_edit += array( 'fields[_add_existing_field][widget_type]' => 'test_field_widget', ); $label = $initial_edit['fields[_add_existing_field][label]']; $field_name = $initial_edit['fields[_add_existing_field][field_name]']; // First step : 'Add existing field' on the 'Manage fields' page. $this->drupalPost("$bundle_path/fields", $initial_edit, t('Save')); // Second step : 'Instance settings' form. $this->drupalPost(NULL, $instance_edit, t('Save settings')); $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), 'Redirected to "Manage fields" page.'); // Check that the field appears in the overview form. $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $label, 'Field was created and appears in the overview page.'); } /** * Deletes a field instance through the Field UI. * * @param $bundle_path * Admin path of the bundle that the field instance is to be deleted from. * @param $field_name * The name of the field. * @param $label * The label of the field. * @param $bundle_label * The label of the bundle. */ function fieldUIDeleteField($bundle_path, $field_name, $label, $bundle_label) { // Display confirmation form. $this->drupalGet("$bundle_path/fields/$field_name/delete"); $this->assertRaw(t('Are you sure you want to delete the field %label', array('%label' => $label)), 'Delete confirmation was found.'); // Submit confirmation form. $this->drupalPost(NULL, array(), t('Delete')); $this->assertRaw(t('The field %label has been deleted from the %type content type.', array('%label' => $label, '%type' => $bundle_label)), 'Delete message was found.'); // Check that the field does not appear in the overview form. $this->assertNoFieldByXPath('//table[@id="field-overview"]//span[@class="label-field"]', $label, 'Field does not appear in the overview page.'); } } /** * Tests the functionality of the 'Manage fields' screen. */ class FieldUIManageFieldsTestCase extends FieldUITestCase { 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; } /** * 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(); } /** * Tests the manage fields page. */ function manageFieldsPage() { $this->drupalGet('admin/structure/types/manage/' . $this->hyphen_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 . '', format_string('%table_header table header was found.', array('%table_header' => $table_header))); } // "Add new field" and "Add existing field" aren't a table heading so just // test the text. foreach (array('Add new field', 'Add 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->hyphen_type, $edit); // Assert the field appears in the "add 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(1); $this->drupalGet('admin/structure/taxonomy/' . $vocabulary->machine_name . '/fields'); $this->assertTrue($this->xpath('//select[@name="fields[_add_existing_field][field_name]"]//option[@value="' . $this->field_name . '"]'), 'Existing field was found in account settings.'); } /** * Tests editing an existing field. */ function updateField() { // Go to the field edit page. $this->drupalGet('admin/structure/types/manage/' . $this->hyphen_type . '/fields/' . $this->field_name); // Populate the field settings with new settings. $string = 'updated dummy test string'; $edit = array( 'field[settings][test_field_setting]' => $string, '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->assertText(t('Saved @label configuration.', array('@label' => $this->field_label)), 'Redirected to "Manage fields" page.'); } /** * Tests adding an existing field in another content type. */ function addExistingField() { // Check "Add existing field" appears. $this->drupalGet('admin/structure/types/manage/page/fields'); $this->assertRaw(t('Add existing field'), '"Add 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); } /** * 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.'); $this->assertTrue($instance['widget']['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, ); field_create_instance($instance); $langcode = LANGUAGE_NONE; $admin_path = 'admin/structure/types/manage/' . $this->hyphen_type . '/fields/' . $field_name; $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.'); $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.'); } /** * Tests that deletion removes fields and instances as expected. */ function testDeleteField() { // Create a new field. $bundle_path1 = 'admin/structure/types/manage/' . $this->hyphen_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; $hyphen_type2 = str_replace('_', '-', $type_name2); // Add an instance to the second node type. $bundle_path2 = 'admin/structure/types/manage/' . $hyphen_type2; $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, $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, $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 the 'no_ui' option in hook_field_info(). */ function testHiddenFields() { $bundle_path = 'admin/structure/types/manage/' . $this->hyphen_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'), 'widget' => array('type' => 'test_field_widget'), ); field_create_instance($instance); $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 'add 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 'add existing field' select respects field types 'no_ui' property."); } /** * Tests renaming a bundle. */ function testRenameBundle() { $type2 = strtolower($this->randomName(8)) . '_' .'test'; $hyphen_type2 = str_replace('_', '-', $type2); $options = array( 'type' => $type2, ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type, $options, t('Save content type')); $this->drupalGet('admin/structure/types/manage/' . $hyphen_type2 . '/fields'); } /** * 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->hyphen_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 that external URLs in the 'destinations' query parameter are blocked. */ function testExternalDestinations() { $path = 'admin/structure/types/manage/article/fields/field_tags/field-settings'; $options = array( 'query' => array('destinations' => array('http://example.com')), ); $this->drupalPost($path, NULL, t('Save field settings'), $options); $this->assertUrl('admin/structure/types/manage/article/fields', array(), 'Stayed on the same site.'); } } /** * Tests the functionality of the 'Manage display' screens. */ class FieldUIManageDisplayTestCase extends FieldUITestCase { public static function getInfo() { return array( 'name' => 'Manage display', 'description' => 'Test the Field UI "Manage display" screens.', 'group' => 'Field UI', ); } function setUp() { parent::setUp(array('search')); } /** * Tests formatter settings. */ function testFormatterUI() { $manage_fields = 'admin/structure/types/manage/' . $this->hyphen_type; $manage_display = $manage_fields . '/display'; // Create a field, and a node with some data for the field. $edit = array( 'fields[_add_new_field][label]' => 'Test field', 'fields[_add_new_field][field_name]' => 'test', ); $this->fieldUIAddNewField($manage_fields, $edit); // Clear the test-side cache and get the saved field instance. field_info_cache_clear(); $instance = field_info_instance('node', 'field_test', $this->type); $format = $instance['display']['default']['type']; $default_settings = field_info_formatter_settings($format); $setting_name = key($default_settings); $setting_value = $instance['display']['default']['settings'][$setting_name]; // Display the "Manage display" screen and check that the expected formatter is // selected. $this->drupalGet($manage_display); $this->assertFieldByName('fields[field_test][type]', $format, 'The expected formatter is selected.'); $this->assertText("$setting_name: $setting_value", 'The expected summary is displayed.'); // Change the formatter and check that the summary is updated. $edit = array('fields[field_test][type]' => 'field_test_multiple', 'refresh_rows' => 'field_test'); $this->drupalPostAJAX(NULL, $edit, array('op' => t('Refresh'))); $format = 'field_test_multiple'; $default_settings = field_info_formatter_settings($format); $setting_name = key($default_settings); $setting_value = $default_settings[$setting_name]; $this->assertFieldByName('fields[field_test][type]', $format, 'The expected formatter is selected.'); $this->assertText("$setting_name: $setting_value", 'The expected summary is displayed.'); // Submit the form and check that the instance is updated. $this->drupalPost(NULL, array(), t('Save')); field_info_cache_clear(); $instance = field_info_instance('node', 'field_test', $this->type); $current_format = $instance['display']['default']['type']; $current_setting_value = $instance['display']['default']['settings'][$setting_name]; $this->assertEqual($current_format, $format, 'The formatter was updated.'); $this->assertEqual($current_setting_value, $setting_value, 'The setting was updated.'); } /** * Tests switching view modes to use custom or 'default' settings'. */ function testViewModeCustom() { // Create a field, and a node with some data for the field. $edit = array( 'fields[_add_new_field][label]' => 'Test field', 'fields[_add_new_field][field_name]' => 'test', ); $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->hyphen_type, $edit); // For this test, use a formatter setting value that is an integer unlikely // to appear in a rendered node other than as part of the field being tested // (for example, unlikely to be part of the "Submitted by ... on ..." line). $value = 12345; $settings = array( 'type' => $this->type, 'field_test' => array(LANGUAGE_NONE => array(array('value' => $value))), ); $node = $this->drupalCreateNode($settings); // Gather expected output values with the various formatters. $formatters = field_info_formatter_types(); $output = array( 'field_test_default' => $formatters['field_test_default']['settings']['test_formatter_setting'] . '|' . $value, 'field_test_with_prepare_view' => $formatters['field_test_with_prepare_view']['settings']['test_formatter_setting_additional'] . '|' . $value. '|' . ($value + 1), ); // Check that the field is displayed with the default formatter in 'rss' // mode (uses 'default'), and hidden in 'teaser' mode (uses custom settings). $this->assertNodeViewText($node, 'rss', $output['field_test_default'], "The field is displayed as expected in view modes that use 'default' settings."); $this->assertNodeViewNoText($node, 'teaser', $value, "The field is hidden in view modes that use custom settings."); // Change fomatter for 'default' mode, check that the field is displayed // accordingly in 'rss' mode. $edit = array( 'fields[field_test][type]' => 'field_test_with_prepare_view', ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type . '/display', $edit, t('Save')); $this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected in view modes that use 'default' settings."); // Specialize the 'rss' mode, check that the field is displayed the same. $edit = array( "view_modes_custom[rss]" => TRUE, ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type . '/display', $edit, t('Save')); $this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected in newly specialized 'rss' mode."); // Set the field to 'hidden' in the view mode, check that the field is // hidden. $edit = array( 'fields[field_test][type]' => 'hidden', ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type . '/display/rss', $edit, t('Save')); $this->assertNodeViewNoText($node, 'rss', $value, "The field is hidden in 'rss' mode."); // Set the view mode back to 'default', check that the field is displayed // accordingly. $edit = array( "view_modes_custom[rss]" => FALSE, ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type . '/display', $edit, t('Save')); $this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected when 'rss' mode is set back to 'default' settings."); // Specialize the view mode again. $edit = array( "view_modes_custom[rss]" => TRUE, ); $this->drupalPost('admin/structure/types/manage/' . $this->hyphen_type . '/display', $edit, t('Save')); // Check that the previous settings for the view mode have been kept. $this->assertNodeViewNoText($node, 'rss', $value, "The previous settings are kept when 'rss' mode is specialized again."); } /** * Asserts that a string is found in the rendered node in a view mode. * * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. * @param $text * Plain text to look for. * @param $message * Message to display. * * @return * TRUE on pass, FALSE on fail. */ function assertNodeViewText($node, $view_mode, $text, $message) { return $this->assertNodeViewTextHelper($node, $view_mode, $text, $message, FALSE); } /** * Asserts that a string is not found in the rendered node in a view mode. * * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. * @param $text * Plain text to look for. * @param $message * Message to display. * @return * TRUE on pass, FALSE on fail. */ function assertNodeViewNoText($node, $view_mode, $text, $message) { return $this->assertNodeViewTextHelper($node, $view_mode, $text, $message, TRUE); } /** * Asserts that a string is (not) found in the rendered nodein a view mode. * * This helper function is used by assertNodeViewText() and * assertNodeViewNoText(). * * @param $node * The node. * @param $view_mode * The view mode in which the node should be displayed. * @param $text * Plain text to look for. * @param $message * Message to display. * @param $not_exists * TRUE if this text should not exist, FALSE if it should. * * @return * TRUE on pass, FALSE on fail. */ function assertNodeViewTextHelper($node, $view_mode, $text, $message, $not_exists) { // Make sure caches on the tester side are refreshed after changes // submitted on the tested side. field_info_cache_clear(); // Save current content so that we can restore it when we're done. $old_content = $this->drupalGetContent(); // Render a cloned node, so that we do not alter the original. $clone = clone $node; $element = node_view($clone, $view_mode); $output = drupal_render($element); $this->verbose(t('Rendered node - view mode: @view_mode', array('@view_mode' => $view_mode)) . '