3 namespace Drupal\Tests\field\Functional\EntityReference;
5 use Behat\Mink\Element\NodeElement;
6 use Drupal\Core\Field\FieldStorageDefinitionInterface;
7 use Drupal\field\Entity\FieldConfig;
8 use Drupal\node\Entity\Node;
9 use Drupal\taxonomy\Entity\Vocabulary;
10 use Drupal\Tests\BrowserTestBase;
11 use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
14 * Tests for the administrative UI.
16 * @group entity_reference
18 class EntityReferenceAdminTest extends BrowserTestBase {
25 * Enable path module to ensure that the selection handler does not fail for
26 * entities with a path field.
27 * Enable views_ui module to see the no_view_help text.
31 public static $modules = ['node', 'field_ui', 'path', 'taxonomy', 'block', 'views_ui'];
34 * The name of the content type created for testing purposes.
43 protected function setUp() {
45 $this->drupalPlaceBlock('system_breadcrumb_block');
47 // Create a content type, with underscores.
48 $type_name = strtolower($this->randomMachineName(8)) . '_test';
49 $type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
50 $this->type = $type->id();
53 $admin_user = $this->drupalCreateUser([
55 'administer node fields',
56 'administer node display',
58 'create ' . $type_name . ' content',
59 'edit own ' . $type_name . ' content',
61 $this->drupalLogin($admin_user);
65 * Tests the Entity Reference Admin UI.
67 public function testFieldAdminHandler() {
68 $bundle_path = 'admin/structure/types/manage/' . $this->type;
69 // Create a new view and display it as a entity reference.
71 'id' => 'node_test_view',
72 'label' => 'Node Test View',
73 'show[wizard_key]' => 'node',
74 'show[sort]' => 'none',
76 'page[title]' => 'Test Node View',
77 'page[path]' => 'test/node/view',
78 'page[style][style_plugin]' => 'default',
79 'page[style][row_plugin]' => 'fields',
81 $this->drupalPostForm('admin/structure/views/add', $edit, t('Save and edit'));
82 $this->drupalPostForm(NULL, [], t('Duplicate as Entity Reference'));
83 $this->clickLink(t('Settings'));
85 'style_options[search_fields][title]' => 'title',
87 $this->drupalPostForm(NULL, $edit, t('Apply'));
89 // Set sort to NID ascending.
91 'name[node_field_data.nid]' => 1,
93 $this->drupalPostForm('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort', $edit, t('Add and configure sort criteria'));
94 $this->drupalPostForm(NULL, NULL, t('Apply'));
96 $this->drupalPostForm('admin/structure/views/view/node_test_view/edit/entity_reference_1', [], t('Save'));
97 $this->clickLink(t('Settings'));
99 // Create a test entity reference field.
100 $field_name = 'test_entity_ref_field';
102 'new_storage_type' => 'field_ui:entity_reference:node',
103 'label' => 'Test Entity Reference Field',
104 'field_name' => $field_name,
106 $this->drupalPostForm($bundle_path . '/fields/add-field', $edit, t('Save and continue'));
110 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
112 $this->drupalPostForm(NULL, $edit, t('Save field settings'));
114 // Add the view to the test field.
116 'settings[handler]' => 'views',
118 $this->drupalPostForm(NULL, $edit, t('Change handler'));
121 'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
123 $this->drupalPostForm(NULL, $edit, t('Save settings'));
126 $node1 = Node::create([
127 'type' => $this->type,
128 'title' => 'Foo Node',
131 $node2 = Node::create([
132 'type' => $this->type,
133 'title' => 'Foo Node',
137 // Try to add a new node and fill the entity reference field.
138 $this->drupalGet('node/add/' . $this->type);
139 $result = $this->xpath('//input[@name="field_test_entity_ref_field[0][target_id]" and contains(@data-autocomplete-path, "/entity_reference_autocomplete/node/views/")]');
140 $target_url = $this->getAbsoluteUrl($result[0]->getAttribute('data-autocomplete-path'));
141 $this->drupalGet($target_url, ['query' => ['q' => 'Foo']]);
142 $this->assertRaw($node1->getTitle() . ' (' . $node1->id() . ')');
143 $this->assertRaw($node2->getTitle() . ' (' . $node2->id() . ')');
145 // Try to add a new node, fill the entity reference field and submit the
147 $this->drupalPostForm('node/add/' . $this->type, [], t('Add another item'));
149 'title[0][value]' => 'Example',
150 'field_test_entity_ref_field[0][target_id]' => 'Foo Node (' . $node1->id() . ')',
151 'field_test_entity_ref_field[1][target_id]' => 'Foo Node (' . $node2->id() . ')',
153 $this->drupalPostForm(NULL, $edit, t('Save'));
154 $this->assertResponse(200);
157 'title[0][value]' => 'Example',
158 'field_test_entity_ref_field[0][target_id]' => 'Test',
160 $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
162 // Assert that entity reference autocomplete field is validated.
163 $this->assertText(t('There are no entities matching "@entity"', ['@entity' => 'Test']));
166 'title[0][value]' => 'Test',
167 'field_test_entity_ref_field[0][target_id]' => $node1->getTitle(),
169 $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
171 // Assert the results multiple times to avoid sorting problem of nodes with
173 $this->assertText(t('Multiple entities match this reference;'));
174 $this->assertText(t("@node1", ['@node1' => $node1->getTitle() . ' (' . $node1->id() . ')']));
175 $this->assertText(t("@node2", ['@node2' => $node2->getTitle() . ' (' . $node2->id() . ')']));
176 $this->assertText(t('Specify the one you want by appending the id in parentheses, like "@example".', ['@example' => $node2->getTitle() . ' (' . $node2->id() . ')']));
179 'title[0][value]' => 'Test',
180 'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . ' (' . $node1->id() . ')',
182 $this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
183 $this->assertLink($node1->getTitle());
185 // Tests adding default values to autocomplete widgets.
186 Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
187 $taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
188 $field_path = 'node.' . $this->type . '.field_' . $taxonomy_term_field_name;
189 $this->drupalGet($bundle_path . '/fields/' . $field_path . '/storage');
193 $this->drupalPostForm(NULL, $edit, t('Save field settings'));
194 $this->drupalGet($bundle_path . '/fields/' . $field_path);
195 $term_name = $this->randomString();
196 $result = \Drupal::entityQuery('taxonomy_term')
197 ->condition('name', $term_name)
198 ->condition('vid', 'tags')
201 $this->assertIdentical(0, count($result), "No taxonomy terms exist with the name '$term_name'.");
203 // This must be set before new entities will be auto-created.
204 'settings[handler_settings][auto_create]' => 1,
206 $this->drupalPostForm(NULL, $edit, t('Save settings'));
207 $this->drupalGet($bundle_path . '/fields/' . $field_path);
209 // A term that doesn't yet exist.
210 'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
212 $this->drupalPostForm(NULL, $edit, t('Save settings'));
213 // The term should now exist.
214 $result = \Drupal::entityQuery('taxonomy_term')
215 ->condition('name', $term_name)
216 ->condition('vid', 'tags')
219 $this->assertIdentical(1, count($result), 'Taxonomy term was auto created when set as field default.');
223 * Tests the formatters for the Entity References.
225 public function testAvailableFormatters() {
226 // Create a new vocabulary.
227 Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
229 // Create entity reference field with taxonomy term as a target.
230 $taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
232 // Create entity reference field with user as a target.
233 $user_field_name = $this->createEntityReferenceField('user');
235 // Create entity reference field with node as a target.
236 $node_field_name = $this->createEntityReferenceField('node', [$this->type]);
238 // Create entity reference field with date format as a target.
239 $date_format_field_name = $this->createEntityReferenceField('date_format');
241 // Display all newly created Entity Reference configuration.
242 $this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
244 // Check for Taxonomy Term select box values.
245 // Test if Taxonomy Term Entity Reference Field has the correct formatters.
246 $this->assertFieldSelectOptions('fields[field_' . $taxonomy_term_field_name . '][type]', [
247 'entity_reference_label',
248 'entity_reference_entity_id',
249 'entity_reference_rss_category',
250 'entity_reference_entity_view',
253 // Test if User Reference Field has the correct formatters.
254 // Author should be available for this field.
255 // RSS Category should not be available for this field.
256 $this->assertFieldSelectOptions('fields[field_' . $user_field_name . '][type]', [
258 'entity_reference_entity_id',
259 'entity_reference_entity_view',
260 'entity_reference_label',
263 // Test if Node Entity Reference Field has the correct formatters.
264 // RSS Category should not be available for this field.
265 $this->assertFieldSelectOptions('fields[field_' . $node_field_name . '][type]', [
266 'entity_reference_label',
267 'entity_reference_entity_id',
268 'entity_reference_entity_view',
271 // Test if Date Format Reference Field has the correct formatters.
272 // RSS Category & Entity View should not be available for this field.
273 // This could be any field without a ViewBuilder.
274 $this->assertFieldSelectOptions('fields[field_' . $date_format_field_name . '][type]', [
275 'entity_reference_label',
276 'entity_reference_entity_id',
281 * Tests field settings for an entity reference field when the field has
282 * multiple target bundles and is set to auto-create the target entity.
284 public function testMultipleTargetBundles() {
285 /** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
287 for ($i = 0; $i < 2; $i++) {
288 $vid = mb_strtolower($this->randomMachineName());
289 $vocabularies[$i] = Vocabulary::create([
290 'name' => $this->randomString(),
293 $vocabularies[$i]->save();
296 // Create a new field pointing to the first vocabulary.
297 $field_name = $this->createEntityReferenceField('taxonomy_term', [$vocabularies[0]->id()]);
298 $field_name = "field_$field_name";
299 $field_id = 'node.' . $this->type . '.' . $field_name;
300 $path = 'admin/structure/types/manage/' . $this->type . '/fields/' . $field_id;
302 $this->drupalGet($path);
304 // Expect that there's no 'auto_create_bundle' selected.
305 $this->assertNoFieldByName('settings[handler_settings][auto_create_bundle]');
308 'settings[handler_settings][target_bundles][' . $vocabularies[1]->id() . ']' => TRUE,
310 // Enable the second vocabulary as a target bundle.
311 $this->drupalPostForm($path, $edit, 'Save settings');
312 $this->drupalGet($path);
313 // Expect a select element with the two vocabularies as options.
314 $this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[0]->id() . "']");
315 $this->assertFieldByXPath("//select[@name='settings[handler_settings][auto_create_bundle]']/option[@value='" . $vocabularies[1]->id() . "']");
318 'settings[handler_settings][auto_create]' => TRUE,
319 'settings[handler_settings][auto_create_bundle]' => $vocabularies[1]->id(),
321 $this->drupalPostForm(NULL, $edit, t('Save settings'));
323 /** @var \Drupal\field\Entity\FieldConfig $field_config */
324 $field_config = FieldConfig::load($field_id);
325 // Expect that the target bundle has been saved in the backend.
326 $this->assertEqual($field_config->getSetting('handler_settings')['auto_create_bundle'], $vocabularies[1]->id());
328 // Delete the other bundle. Field config should not be affected.
329 $vocabularies[0]->delete();
330 $field_config = FieldConfig::load($field_id);
331 $this->assertTrue($field_config->getSetting('handler_settings')['auto_create']);
332 $this->assertIdentical($field_config->getSetting('handler_settings')['auto_create_bundle'], $vocabularies[1]->id());
334 // Delete the bundle set for entity auto-creation. Auto-created settings
335 // should be reset (no auto-creation).
336 $vocabularies[1]->delete();
337 $field_config = FieldConfig::load($field_id);
338 $this->assertFalse($field_config->getSetting('handler_settings')['auto_create']);
339 $this->assertFalse(isset($field_config->getSetting('handler_settings')['auto_create_bundle']));
343 * Creates a new Entity Reference fields with a given target type.
345 * @param string $target_type
346 * The name of the target type
347 * @param string[] $bundles
348 * A list of bundle IDs. Defaults to [].
351 * Returns the generated field name
353 protected function createEntityReferenceField($target_type, $bundles = []) {
354 // Generates a bundle path for the newly created content type.
355 $bundle_path = 'admin/structure/types/manage/' . $this->type;
357 // Generate a random field name, must be only lowercase characters.
358 $field_name = strtolower($this->randomMachineName());
360 $storage_edit = $field_edit = [];
361 $storage_edit['settings[target_type]'] = $target_type;
362 foreach ($bundles as $bundle) {
363 $field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
366 $this->fieldUIAddNewField($bundle_path, $field_name, NULL, 'entity_reference', $storage_edit, $field_edit);
368 // Returns the generated field name.
373 * Checks if a select element contains the specified options.
375 * @param string $name
377 * @param array $expected_options
378 * An array of expected options.
380 protected function assertFieldSelectOptions($name, array $expected_options) {
381 $xpath = $this->buildXPathQuery('//select[@name=:name]', [':name' => $name]);
382 $fields = $this->xpath($xpath);
385 $options = $field->findAll('xpath', 'option');
386 array_walk($options, function (NodeElement &$option) {
387 $option = $option->getValue();
390 sort($expected_options);
391 $this->assertIdentical($options, $expected_options);
394 $this->fail('Unable to find field ' . $name);