Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Boolean;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests boolean field functionality.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field_ui',
|
||||
'options',
|
||||
'field_test_boolean_access_denied',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A field to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field used in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test form display',
|
||||
'administer entity_test fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests boolean field.
|
||||
*/
|
||||
public function testBooleanField(): void {
|
||||
$on = $this->randomMachineName();
|
||||
$off = $this->randomMachineName();
|
||||
$label = $this->randomMachineName();
|
||||
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $label,
|
||||
'required' => TRUE,
|
||||
'settings' => [
|
||||
'on_label' => $on,
|
||||
'off_label' => $off,
|
||||
],
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
])
|
||||
->save();
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[value]", '');
|
||||
$this->assertSession()->pageTextContains($this->field->label());
|
||||
$this->assertSession()->responseNotContains($on);
|
||||
|
||||
// Submit and ensure it is accepted.
|
||||
$edit = [
|
||||
"{$field_name}[value]" => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Verify that boolean value is displayed.
|
||||
$entity = EntityTest::load($id);
|
||||
$this->drupalGet($entity->toUrl());
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
|
||||
// Test with "On" label option.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
'settings' => [
|
||||
'display_label' => FALSE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[value]", '');
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
$this->assertSession()->pageTextNotContains($this->field->label());
|
||||
|
||||
// Test if we can change the on label.
|
||||
$on = $this->randomMachineName();
|
||||
$edit = [
|
||||
'settings[on_label]' => $on,
|
||||
];
|
||||
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
// Check if we see the updated labels in the creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->pageTextContains($on);
|
||||
|
||||
// Go to the form display page and check if the default settings works as
|
||||
// expected.
|
||||
$fieldEditUrl = 'entity_test/structure/entity_test/form-display';
|
||||
$this->drupalGet($fieldEditUrl);
|
||||
|
||||
// Click on the widget settings button to open the widget settings form.
|
||||
$this->submitForm([], $field_name . "_settings_edit");
|
||||
|
||||
$this->assertSession()->pageTextContains('Use field label instead of the "On" label as the label.');
|
||||
|
||||
// Enable setting.
|
||||
$edit = ['fields[' . $field_name . '][settings_edit_form][settings][display_label]' => 1];
|
||||
$this->submitForm($edit, $field_name . "_plugin_settings_update");
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Go again to the form display page and check if the setting
|
||||
// is stored and has the expected effect.
|
||||
$this->drupalGet($fieldEditUrl);
|
||||
$this->assertSession()->pageTextContains('Use field label: Yes');
|
||||
|
||||
$this->submitForm([], $field_name . "_settings_edit");
|
||||
$this->assertSession()->pageTextContains('Use field label instead of the "On" label as the label.');
|
||||
$this->getSession()->getPage()->hasCheckedField('fields[' . $field_name . '][settings_edit_form][settings][display_label]');
|
||||
|
||||
// Test the boolean field settings.
|
||||
$this->drupalGet('entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-on-label', $on);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-off-label', $off);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field access.
|
||||
*/
|
||||
public function testFormAccess(): void {
|
||||
$on = 'boolean_on';
|
||||
$off = 'boolean_off';
|
||||
$label = 'boolean_label';
|
||||
$field_name = 'boolean_name';
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $label,
|
||||
'settings' => [
|
||||
'on_label' => $on,
|
||||
'off_label' => $off,
|
||||
],
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean_checkbox',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'boolean',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldExists("{$field_name}[value]");
|
||||
|
||||
// Should be posted OK.
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Tell the test module to disable access to the field.
|
||||
\Drupal::state()->set('field.test_boolean_field_access_field', $field_name);
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Field should not be there anymore.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[value]");
|
||||
// Should still be able to post the form.
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Boolean;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Boolean field formatter settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFormatterSettingsTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'text', 'node', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the entity bundle that is created in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* The name of the Boolean field to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a content type. Use Node because it has Field UI pages that work.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->bundle = $type->id();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer nodes',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->fieldName = $this->randomMachineName(8);
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('node', $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'boolean',
|
||||
'settings' => [],
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatter settings page for the Boolean formatter.
|
||||
*/
|
||||
public function testBooleanFormatterSettings(): void {
|
||||
// List the options we expect to see on the settings form. Omit the one
|
||||
// with the Unicode check/x characters, which does not appear to work
|
||||
// well in BrowserTestBase.
|
||||
$options = [
|
||||
'Yes / No',
|
||||
'True / False',
|
||||
'On / Off',
|
||||
'Enabled / Disabled',
|
||||
'1 / 0',
|
||||
'Custom',
|
||||
];
|
||||
|
||||
// For several different values of the field settings, test that the
|
||||
// options, including default, are shown correctly.
|
||||
$settings = [
|
||||
['Yes', 'No'],
|
||||
['On', 'Off'],
|
||||
['TRUE', 'FALSE'],
|
||||
];
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
foreach ($settings as $values) {
|
||||
// Set up the field settings.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/fields/node.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm([
|
||||
'settings[on_label]' => $values[0],
|
||||
'settings[off_label]' => $values[1],
|
||||
], 'Save settings');
|
||||
|
||||
// Open the Manage Display page and trigger the field settings form.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->submitForm([], $this->fieldName . '_settings_edit');
|
||||
|
||||
// Test that the settings options are present in the correct format.
|
||||
foreach ($options as $string) {
|
||||
$assert_session->pageTextContains($string);
|
||||
}
|
||||
$assert_session->pageTextContains("Field settings ({$values[0]} / {$values[1]})");
|
||||
|
||||
// Test that the settings summary are present in the correct format.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->assertSession()->elementExists('xpath', "//div[contains(@class, 'field-plugin-summary')]");
|
||||
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, 'field-plugin-summary')]", "Display: {$values[0]} / {$values[1]}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Email;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests email field functionality.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class EmailFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A field storage to use in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field used in this test class.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer content types',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests email field.
|
||||
*/
|
||||
public function testEmailField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'email',
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
$this->field = FieldConfig::create([
|
||||
'field_storage' => $this->fieldStorage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'email_default',
|
||||
'settings' => [
|
||||
'placeholder' => 'example@example.com',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
// Create a display for the full view mode.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'email_mailto',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="example@example.com"');
|
||||
|
||||
// Submit a valid email address and ensure it is accepted.
|
||||
$value = 'test@example.com';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($value);
|
||||
|
||||
// Verify that a mailto link is displayed.
|
||||
$entity = EntityTest::load($id);
|
||||
$display = $display_repository->getViewDisplay($entity->getEntityTypeId(), $entity->bundle(), 'full');
|
||||
$content = $display->build($entity);
|
||||
$rendered_content = (string) \Drupal::service('renderer')->renderRoot($content);
|
||||
$this->assertStringContainsString('href="mailto:test@example.com"', $rendered_content);
|
||||
|
||||
// Test Email validation message.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$value = 'abc.@in';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusMessageContains("The email address {$value} is not valid. Use the format user@example.com.", 'error');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,431 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Messenger\MessengerInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable path module to ensure that the selection handler does not fail for
|
||||
* entities with a path field.
|
||||
* Enable views_ui module to see the no_view_help text.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'path',
|
||||
'taxonomy',
|
||||
'block',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the content type created for testing purposes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
|
||||
// Create a content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer views',
|
||||
'create ' . $type_name . ' content',
|
||||
'edit own ' . $type_name . ' content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Entity Reference Admin UI.
|
||||
*/
|
||||
public function testFieldAdminHandler(): void {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
// Create a new view and display it as an entity reference.
|
||||
$edit = [
|
||||
'id' => 'node_test_view',
|
||||
'label' => 'Node Test View',
|
||||
'show[wizard_key]' => 'node',
|
||||
'show[sort]' => 'none',
|
||||
'page[create]' => 1,
|
||||
'page[title]' => 'Test Node View',
|
||||
'page[path]' => 'test/node/view',
|
||||
'page[style][style_plugin]' => 'default',
|
||||
'page[style][row_plugin]' => 'fields',
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/add');
|
||||
$this->submitForm($edit, 'Save and edit');
|
||||
$this->submitForm([], 'Duplicate as Entity Reference');
|
||||
$this->clickLink('Settings');
|
||||
$edit = [
|
||||
'style_options[search_fields][title]' => 'title',
|
||||
];
|
||||
$this->submitForm($edit, 'Apply');
|
||||
|
||||
// Set sort to NID ascending.
|
||||
$edit = [
|
||||
'name[node_field_data.nid]' => 1,
|
||||
];
|
||||
$this->drupalGet('admin/structure/views/nojs/add-handler/node_test_view/entity_reference_1/sort');
|
||||
$this->submitForm($edit, 'Add and configure sort criteria');
|
||||
$this->submitForm([], 'Apply');
|
||||
|
||||
$this->drupalGet('admin/structure/views/view/node_test_view/edit/entity_reference_1');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->clickLink('Settings');
|
||||
|
||||
// Create a test entity reference field.
|
||||
$field_name = 'test_entity_ref_field';
|
||||
$this->fieldUIAddNewField($bundle_path, $field_name, 'Test Entity Reference Field', 'field_ui:entity_reference:node', [], [], FALSE);
|
||||
|
||||
// Set to unlimited.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
|
||||
// Add the view to the test field.
|
||||
$edit = [
|
||||
'settings[handler]' => 'views',
|
||||
];
|
||||
$this->submitForm($edit, 'Change handler');
|
||||
$edit = [
|
||||
'required' => FALSE,
|
||||
'settings[handler_settings][view][view_and_display]' => 'node_test_view:entity_reference_1',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusMessageContains("Saved Test Entity Reference Field configuration.", MessengerInterface::TYPE_STATUS);
|
||||
$this->assertFieldExistsOnOverview('Test Entity Reference Field');
|
||||
|
||||
// Create nodes.
|
||||
$node1 = Node::create([
|
||||
'type' => $this->type,
|
||||
'title' => 'Foo Node',
|
||||
]);
|
||||
$node1->save();
|
||||
$node2 = Node::create([
|
||||
'type' => $this->type,
|
||||
'title' => 'Foo Node',
|
||||
]);
|
||||
$node2->save();
|
||||
|
||||
// Try to add a new node and fill the entity reference field.
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$field = $this->assertSession()->fieldExists('field_test_entity_ref_field[0][target_id]');
|
||||
$this->assertStringContainsString("/entity_reference_autocomplete/node/views/", $field->getAttribute('data-autocomplete-path'));
|
||||
$target_url = $this->getAbsoluteUrl($field->getAttribute('data-autocomplete-path'));
|
||||
$this->drupalGet($target_url, ['query' => ['q' => 'Foo']]);
|
||||
$this->assertSession()->pageTextContains($node1->getTitle() . ' (' . $node1->id() . ')');
|
||||
$this->assertSession()->pageTextContains($node2->getTitle() . ' (' . $node2->id() . ')');
|
||||
|
||||
// Try to add a new node, fill the entity reference field and submit the
|
||||
// form.
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm([], 'Add another item');
|
||||
$edit = [
|
||||
'title[0][value]' => 'Example',
|
||||
'field_test_entity_ref_field[0][target_id]' => 'Foo Node (' . $node1->id() . ')',
|
||||
'field_test_entity_ref_field[1][target_id]' => 'Foo Node (' . $node2->id() . ')',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Example',
|
||||
'field_test_entity_ref_field[0][target_id]' => 'Test',
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert that entity reference autocomplete field is validated.
|
||||
$this->assertSession()->pageTextContains('There are no content items matching "Test"');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test',
|
||||
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle(),
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert the results multiple times to avoid sorting problem of nodes with
|
||||
// the same title.
|
||||
$this->assertSession()->pageTextContains('Multiple content items match this reference;');
|
||||
$this->assertSession()->pageTextContains($node1->getTitle() . ' (' . $node1->id() . ')');
|
||||
$this->assertSession()->pageTextContains($node2->getTitle() . ' (' . $node2->id() . ')');
|
||||
$this->assertSession()->pageTextContains('Specify the one you want by appending the id in parentheses, like "' . $node2->getTitle() . ' (' . $node2->id() . ')".');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => 'Test',
|
||||
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . ' (' . $node1->id() . ')',
|
||||
];
|
||||
$this->drupalGet('node/add/' . $this->type);
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->linkExists($node1->getTitle());
|
||||
|
||||
// Tests adding default values to autocomplete widgets.
|
||||
Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
|
||||
$taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
|
||||
$field_path = 'node.' . $this->type . '.field_' . $taxonomy_term_field_name;
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_path);
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => -1,
|
||||
];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
|
||||
// Assert that the target bundle handler setting is initially set.
|
||||
$this->assertSession()->checkboxChecked('settings[handler_settings][target_bundles][tags]');
|
||||
// Change the handler to 'views'.
|
||||
$this->submitForm([
|
||||
'settings[handler]' => 'views',
|
||||
], 'Change handler');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'views');
|
||||
// Change handler back to 'default'.
|
||||
$this->submitForm([
|
||||
'settings[handler]' => 'default:taxonomy_term',
|
||||
], 'Change handler');
|
||||
// Assert that changing the handler resets the handler settings.
|
||||
$this->assertSession()->checkboxNotChecked('settings[handler_settings][target_bundles][tags]');
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$result = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('name', $term_name)
|
||||
->condition('vid', 'tags')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
$this->assertCount(0, $result, "No taxonomy terms exist with the name '$term_name'.");
|
||||
$edit = [
|
||||
'settings[handler_settings][target_bundles][tags]' => TRUE,
|
||||
// This must be set before new entities will be auto-created.
|
||||
'settings[handler_settings][auto_create]' => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_path);
|
||||
$edit = [
|
||||
'set_default_value' => '1',
|
||||
// A term that doesn't yet exist.
|
||||
'default_value_input[field_' . $taxonomy_term_field_name . '][0][target_id]' => $term_name,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertFieldExistsOnOverview($taxonomy_term_field_name);
|
||||
// The term should now exist.
|
||||
$result = \Drupal::entityQuery('taxonomy_term')
|
||||
->condition('name', $term_name)
|
||||
->condition('vid', 'tags')
|
||||
->accessCheck(FALSE)
|
||||
->execute();
|
||||
$this->assertCount(1, $result, 'Taxonomy term was auto created when set as field default.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatters for the Entity References.
|
||||
*/
|
||||
public function testAvailableFormatters(): void {
|
||||
// Create a new vocabulary.
|
||||
Vocabulary::create(['vid' => 'tags', 'name' => 'tags'])->save();
|
||||
|
||||
// Create entity reference field with taxonomy term as a target.
|
||||
$taxonomy_term_field_name = $this->createEntityReferenceField('taxonomy_term', ['tags']);
|
||||
|
||||
// Create entity reference field with user as a target.
|
||||
$user_field_name = $this->createEntityReferenceField('user');
|
||||
|
||||
// Create entity reference field with node as a target.
|
||||
$node_field_name = $this->createEntityReferenceField('node', [$this->type]);
|
||||
|
||||
// Create entity reference field with date format as a target.
|
||||
$date_format_field_name = $this->createEntityReferenceField('date_format');
|
||||
|
||||
// Display all newly created Entity Reference configuration.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
|
||||
// Check for Taxonomy Term select box values.
|
||||
// Test if Taxonomy Term Entity Reference Field has the correct formatters.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $taxonomy_term_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_rss_category',
|
||||
'entity_reference_entity_view',
|
||||
]);
|
||||
|
||||
// Test if User Reference Field has the correct formatters.
|
||||
// Author should be available for this field.
|
||||
// RSS Category should not be available for this field.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $user_field_name . '][type]', [
|
||||
'author',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_entity_view',
|
||||
'entity_reference_label',
|
||||
]);
|
||||
|
||||
// Test if Node Entity Reference Field has the correct formatters.
|
||||
// RSS Category should not be available for this field.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $node_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
'entity_reference_entity_view',
|
||||
]);
|
||||
|
||||
// Test if Date Format Reference Field has the correct formatters.
|
||||
// RSS Category & Entity View should not be available for this field.
|
||||
// This could be any field without a ViewBuilder.
|
||||
$this->assertFieldSelectOptions('fields[field_' . $date_format_field_name . '][type]', [
|
||||
'entity_reference_label',
|
||||
'entity_reference_entity_id',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field settings for an entity reference field.
|
||||
*
|
||||
* The tested entity reference field has multiple target bundles and is set
|
||||
* to auto-create the target entity.
|
||||
*/
|
||||
public function testMultipleTargetBundles(): void {
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
|
||||
$vocabularies = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$vid = $this->randomMachineName();
|
||||
$vocabularies[$i] = Vocabulary::create([
|
||||
'name' => $this->randomString(),
|
||||
'vid' => $vid,
|
||||
]);
|
||||
$vocabularies[$i]->save();
|
||||
}
|
||||
|
||||
// Create a new field pointing to the first vocabulary.
|
||||
$field_name = $this->createEntityReferenceField('taxonomy_term', [$vocabularies[0]->id()]);
|
||||
$field_name = "field_$field_name";
|
||||
$field_id = 'node.' . $this->type . '.' . $field_name;
|
||||
$path = 'admin/structure/types/manage/' . $this->type . '/fields/' . $field_id;
|
||||
|
||||
$this->drupalGet($path);
|
||||
|
||||
// Expect that there's no 'auto_create_bundle' selected.
|
||||
$this->assertSession()->fieldNotExists('settings[handler_settings][auto_create_bundle]');
|
||||
|
||||
$edit = [
|
||||
'settings[handler_settings][target_bundles][' . $vocabularies[1]->id() . ']' => TRUE,
|
||||
];
|
||||
// Enable the second vocabulary as a target bundle.
|
||||
$this->drupalGet($path);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->drupalGet($path);
|
||||
// Expect a select element with the two vocabularies as options.
|
||||
$this->assertSession()->optionExists('settings[handler_settings][auto_create_bundle]', $vocabularies[0]->id());
|
||||
$this->assertSession()->optionExists('settings[handler_settings][auto_create_bundle]', $vocabularies[1]->id());
|
||||
|
||||
$edit = [
|
||||
'settings[handler_settings][auto_create]' => TRUE,
|
||||
'settings[handler_settings][auto_create_bundle]' => $vocabularies[1]->id(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
// Expect that the target bundle has been saved in the backend.
|
||||
$this->assertEquals($vocabularies[1]->id(), $field_config->getSetting('handler_settings')['auto_create_bundle']);
|
||||
|
||||
// Delete the other bundle. Field config should not be affected.
|
||||
$vocabularies[0]->delete();
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
$this->assertTrue($field_config->getSetting('handler_settings')['auto_create']);
|
||||
$this->assertSame($vocabularies[1]->id(), $field_config->getSetting('handler_settings')['auto_create_bundle']);
|
||||
|
||||
// Delete the bundle set for entity auto-creation. Auto-created settings
|
||||
// should be reset (no auto-creation).
|
||||
$vocabularies[1]->delete();
|
||||
$field_config = FieldConfig::load($field_id);
|
||||
$this->assertFalse($field_config->getSetting('handler_settings')['auto_create']);
|
||||
$this->assertFalse(isset($field_config->getSetting('handler_settings')['auto_create_bundle']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Entity Reference fields with a given target type.
|
||||
*
|
||||
* @param string $target_type
|
||||
* The name of the target type.
|
||||
* @param string[] $bundles
|
||||
* A list of bundle IDs. Defaults to [].
|
||||
*
|
||||
* @return string
|
||||
* Returns the generated field name
|
||||
*/
|
||||
protected function createEntityReferenceField($target_type, $bundles = []) {
|
||||
// Generates a bundle path for the newly created content type.
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
// Generate a random field name, must be only lowercase characters.
|
||||
$field_name = $this->randomMachineName();
|
||||
|
||||
$storage_edit = $field_edit = [];
|
||||
$storage_edit['settings[target_type]'] = $target_type;
|
||||
foreach ($bundles as $bundle) {
|
||||
$field_edit['settings[handler_settings][target_bundles][' . $bundle . ']'] = TRUE;
|
||||
}
|
||||
|
||||
$this->fieldUIAddNewField($bundle_path, $field_name, $field_name, 'entity_reference', $storage_edit, $field_edit);
|
||||
|
||||
// Returns the generated field name.
|
||||
return $field_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSelectOptions(string $name, array $expected_options): void {
|
||||
$options = $this->assertSession()->selectExists($name)->findAll('xpath', 'option');
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getValue();
|
||||
});
|
||||
$this->assertEqualsCanonicalizing($expected_options, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,307 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests creating new entity (e.g. taxonomy-term) from an autocomplete widget.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAutoCreateTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'taxonomy', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of a content type that will reference $referencedType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencingType;
|
||||
|
||||
/**
|
||||
* The name of a content type that will be referenced by $referencingType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create "referencing" and "referenced" node types.
|
||||
$referencing = $this->drupalCreateContentType();
|
||||
$this->referencingType = $referencing->id();
|
||||
|
||||
$referenced = $this->drupalCreateContentType();
|
||||
$this->referencedType = $referenced->id();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'translatable' => FALSE,
|
||||
'entity_types' => [],
|
||||
'settings' => [
|
||||
'target_type' => 'node',
|
||||
],
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'label' => 'Entity reference field',
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $referencing->id(),
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
// Reference a single vocabulary.
|
||||
'target_bundles' => [
|
||||
$referenced->id(),
|
||||
],
|
||||
// Enable auto-create.
|
||||
'auto_create' => TRUE,
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getViewDisplay('node', $referencing->id())
|
||||
->setComponent('test_field')
|
||||
->save();
|
||||
$display_repository->getFormDisplay('node', $referencing->id(), 'default')
|
||||
->setComponent('test_field', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
|
||||
$account = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the autocomplete input element and entity auto-creation.
|
||||
*/
|
||||
public function testAutoCreate(): void {
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$target = $this->assertSession()->fieldExists("edit-test-field-0-target-id");
|
||||
$this->assertTrue($target->hasClass("form-autocomplete"));
|
||||
|
||||
$new_title = $this->randomMachineName();
|
||||
|
||||
// Assert referenced node does not exist.
|
||||
$base_query = \Drupal::entityQuery('node')->accessCheck(FALSE);
|
||||
$base_query
|
||||
->condition('type', $this->referencedType)
|
||||
->condition('title', $new_title);
|
||||
|
||||
$query = clone $base_query;
|
||||
$result = $query->execute();
|
||||
$this->assertEmpty($result, 'Referenced node does not exist yet.');
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'test_field[0][target_id]' => $new_title,
|
||||
];
|
||||
$this->drupalGet("node/add/{$this->referencingType}");
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert referenced node was created.
|
||||
$query = clone $base_query;
|
||||
$result = $query->execute();
|
||||
$this->assertNotEmpty($result, 'Referenced node was created.');
|
||||
$referenced_nid = key($result);
|
||||
$referenced_node = Node::load($referenced_nid);
|
||||
|
||||
// Assert the referenced node is associated with referencing node.
|
||||
$result = \Drupal::entityQuery('node')
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->referencingType)
|
||||
->execute();
|
||||
|
||||
$referencing_nid = key($result);
|
||||
$referencing_node = Node::load($referencing_nid);
|
||||
$this->assertEquals($referenced_nid, $referencing_node->test_field->target_id, 'Newly created node is referenced from the referencing node.');
|
||||
|
||||
// Now try to view the node and check that the referenced node is shown.
|
||||
$this->drupalGet('node/' . $referencing_node->id());
|
||||
$this->assertSession()->pageTextContains($referencing_node->label());
|
||||
$this->assertSession()->pageTextContains($referenced_node->label());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple target bundles.
|
||||
*
|
||||
* Tests if an entity reference field having multiple target bundles is
|
||||
* storing the auto-created entity in the right destination.
|
||||
*/
|
||||
public function testMultipleTargetBundles(): void {
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
|
||||
$vocabularies = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$vid = $this->randomMachineName();
|
||||
$vocabularies[$i] = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $vid,
|
||||
]);
|
||||
$vocabularies[$i]->save();
|
||||
}
|
||||
|
||||
// Create a taxonomy term entity reference field that saves the auto-created
|
||||
// taxonomy terms in the second vocabulary from the two that were configured
|
||||
// as targets.
|
||||
$field_name = $this->randomMachineName();
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$vocabularies[0]->id() => $vocabularies[0]->id(),
|
||||
$vocabularies[1]->id() => $vocabularies[1]->id(),
|
||||
],
|
||||
'auto_create' => TRUE,
|
||||
'auto_create_bundle' => $vocabularies[1]->id(),
|
||||
];
|
||||
$this->createEntityReferenceField('node', $this->referencingType, $field_name, $this->randomString(), 'taxonomy_term', 'default', $handler_settings);
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->referencingType)
|
||||
->setComponent($field_name, ['type' => 'entity_reference_autocomplete'])
|
||||
->save();
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $term_name,
|
||||
'title[0][value]' => $this->randomString(),
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = $term_storage->loadByProperties(['name' => $term_name]);
|
||||
$term = reset($term);
|
||||
|
||||
// The new term is expected to be stored in the second vocabulary.
|
||||
$this->assertEquals($vocabularies[1]->id(), $term->bundle());
|
||||
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::loadByName('node', $this->referencingType, $field_name);
|
||||
$handler_settings = $field_config->getSetting('handler_settings');
|
||||
|
||||
// Change the field setting to store the auto-created terms in the first
|
||||
// vocabulary and test again.
|
||||
$handler_settings['auto_create_bundle'] = $vocabularies[0]->id();
|
||||
$field_config->setSetting('handler_settings', $handler_settings);
|
||||
$field_config->save();
|
||||
|
||||
$term_name = $this->randomString();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $term_name,
|
||||
'title[0][value]' => $this->randomString(),
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
/** @var \Drupal\taxonomy\Entity\Term $term */
|
||||
$term = $term_storage->loadByProperties(['name' => $term_name]);
|
||||
$term = reset($term);
|
||||
|
||||
// The second term is expected to be stored in the first vocabulary.
|
||||
$this->assertEquals($vocabularies[0]->id(), $term->bundle());
|
||||
|
||||
// @todo Re-enable this test when WebTestBase::curlHeaderCallback() provides
|
||||
// a way to catch and assert user-triggered errors.
|
||||
|
||||
// Test the case when the field config settings are inconsistent.
|
||||
// @code
|
||||
// unset($handler_settings['auto_create_bundle']);
|
||||
// $field_config->setSetting('handler_settings', $handler_settings);
|
||||
// $field_config->save();
|
||||
//
|
||||
// $this->drupalGet('node/add/' . $this->referencingType);
|
||||
// $error_message = sprintf(
|
||||
// "Create referenced entities if they don't already exist option is enabled but a specific destination bundle is not set. You should re-visit and fix the settings of the '%s' (%s) field.",
|
||||
// $field_config->getLabel(),
|
||||
// $field_config->getName()
|
||||
// );
|
||||
// $this->assertErrorLogged($error_message);
|
||||
// @endcode
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests autocreation for an entity that has no bundles.
|
||||
*/
|
||||
public function testNoBundles(): void {
|
||||
$account = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$handler_settings = [
|
||||
'auto_create' => TRUE,
|
||||
];
|
||||
$this->createEntityReferenceField('node', $this->referencingType, $field_name, $this->randomString(), 'entity_test_no_bundle_with_label', 'default', $handler_settings);
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->referencingType)
|
||||
->setComponent($field_name, ['type' => 'entity_reference_autocomplete'])
|
||||
->save();
|
||||
|
||||
$node_title = $this->randomMachineName();
|
||||
$name = $this->randomMachineName();
|
||||
$edit = [
|
||||
$field_name . '[0][target_id]' => $name,
|
||||
'title[0][value]' => $node_title,
|
||||
];
|
||||
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert referenced entity was created.
|
||||
$result = \Drupal::entityQuery('entity_test_no_bundle_with_label')
|
||||
->accessCheck(FALSE)
|
||||
->condition('name', $name)
|
||||
->execute();
|
||||
$this->assertNotEmpty($result, 'Referenced entity was created.');
|
||||
$referenced_id = key($result);
|
||||
|
||||
// Assert the referenced entity is associated with referencing node.
|
||||
$result = \Drupal::entityQuery('node')
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->referencingType)
|
||||
->execute();
|
||||
$this->assertCount(1, $result);
|
||||
$referencing_nid = key($result);
|
||||
$referencing_node = Node::load($referencing_nid);
|
||||
$this->assertEquals($referenced_id, $referencing_node->$field_name->target_id, 'Newly created node is referenced from the referencing entity.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Tests\SchemaCheckTestTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests entity reference field default values storage in CMI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFieldDefaultValueTest extends BrowserTestBase {
|
||||
|
||||
use SchemaCheckTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with permission to administer content types, node fields, etc.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create default content type.
|
||||
$this->drupalCreateContentType(['type' => 'reference_content']);
|
||||
$this->drupalCreateContentType(['type' => 'referenced_content']);
|
||||
|
||||
// Create admin user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that default values are correctly translated to UUIDs in config.
|
||||
*/
|
||||
public function testEntityReferenceDefaultValue(): void {
|
||||
// Create a node to be referenced.
|
||||
$referenced_node = $this->drupalCreateNode(['type' => 'referenced_content']);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'entity_reference',
|
||||
'settings' => ['target_type' => 'node'],
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'reference_content',
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'target_bundles' => ['referenced_content'],
|
||||
'sort' => ['field' => '_none'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set created node as default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $field_name . '][0][target_id]' => $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
|
||||
// Check that default value is selected in default value form.
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->assertSession()->responseContains('name="default_value_input[' . $field_name . '][0][target_id]" value="' . $referenced_node->getTitle() . ' (' . $referenced_node->id() . ')');
|
||||
|
||||
// Check if the ID has been converted to UUID in config entity.
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertTrue(isset($config_entity['default_value'][0]['target_uuid']), 'Default value contains target_uuid property');
|
||||
$this->assertEquals($referenced_node->uuid(), $config_entity['default_value'][0]['target_uuid'], 'Content uuid and config entity uuid are the same');
|
||||
// Ensure the configuration has the expected dependency on the entity that
|
||||
// is being used a default value.
|
||||
$this->assertEquals([$referenced_node->getConfigDependencyName()], $config_entity['dependencies']['content']);
|
||||
|
||||
// Clear field definitions cache in order to avoid stale cache values.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Create a new node to check that UUID has been converted to numeric ID.
|
||||
$new_node = Node::create(['type' => 'reference_content']);
|
||||
$this->assertEquals($new_node->get($field_name)->offsetGet(0)->target_id, $referenced_node->id());
|
||||
|
||||
// Ensure that the entity reference config schemas are correct.
|
||||
$field_config = $this->config('field.field.node.reference_content.' . $field_name);
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_config->getName(), $field_config->get());
|
||||
$field_storage_config = $this->config('field.storage.node.' . $field_name);
|
||||
$this->assertConfigSchema(\Drupal::service('config.typed'), $field_storage_config->getName(), $field_storage_config->get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that dependencies due to default values can be removed.
|
||||
*
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::onDependencyRemoval()
|
||||
*/
|
||||
public function testEntityReferenceDefaultConfigValue(): void {
|
||||
// Create a node to be referenced.
|
||||
$referenced_node_type = $this->drupalCreateContentType(['type' => 'referenced_config_to_delete']);
|
||||
$referenced_node_type2 = $this->drupalCreateContentType(['type' => 'referenced_config_to_preserve']);
|
||||
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'entity_reference',
|
||||
'settings' => ['target_type' => 'node_type'],
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'reference_content',
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
'sort' => ['field' => '_none'],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set created node as default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $field_name . '][0][target_id]' => $referenced_node_type->label() . ' (' . $referenced_node_type->id() . ')',
|
||||
'default_value_input[' . $field_name . '][1][target_id]' => $referenced_node_type2->label() . ' (' . $referenced_node_type2->id() . ')',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/reference_content/fields/node.reference_content.' . $field_name);
|
||||
$this->assertSession()->fieldExists("default_value_input[{$field_name}][0][target_id]");
|
||||
$this->assertSession()->fieldNotExists("default_value_input[{$field_name}][1][target_id]");
|
||||
$this->submitForm([], 'Add another item');
|
||||
$this->assertSession()->fieldExists("default_value_input[{$field_name}][1][target_id]");
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
|
||||
// Check that the field has a dependency on the default value.
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertContains($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_delete is a dependency of the field.');
|
||||
$this->assertContains($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_preserve is a dependency of the field.');
|
||||
|
||||
// Check that the field does not have a dependency on the default value
|
||||
// after deleting the node type.
|
||||
$referenced_node_type->delete();
|
||||
$config_entity = $this->config('field.field.node.reference_content.' . $field_name)->get();
|
||||
$this->assertNotContains($referenced_node_type->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_delete not a dependency of the field.');
|
||||
$this->assertContains($referenced_node_type2->getConfigDependencyName(), $config_entity['dependencies']['config'], 'The node type referenced_config_to_preserve is a dependency of the field.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the translation of entity reference field display on nodes.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFieldTranslatedReferenceViewTest extends BrowserTestBase {
|
||||
|
||||
use ContentTranslationTestTrait;
|
||||
|
||||
/**
|
||||
* Flag indicating whether the field is translatable.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $translatable = TRUE;
|
||||
|
||||
/**
|
||||
* The langcode of the source language.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $baseLangcode = 'en';
|
||||
|
||||
/**
|
||||
* Target langcode for translation.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translateToLangcode = 'hu';
|
||||
|
||||
/**
|
||||
* The test entity type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testEntityTypeName = 'node';
|
||||
|
||||
/**
|
||||
* Entity type which have the entity reference field.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $referrerType;
|
||||
|
||||
/**
|
||||
* Entity type which can be referenced.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* The referrer entity.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referrerEntity;
|
||||
|
||||
/**
|
||||
* The entity to refer.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referencedEntityWithoutTranslation;
|
||||
|
||||
/**
|
||||
* The entity to refer.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $referencedEntityWithTranslation;
|
||||
|
||||
/**
|
||||
* The machine name of the entity reference field.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referenceFieldName = 'test_reference_field';
|
||||
|
||||
/**
|
||||
* The label of the untranslated referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $labelOfNotTranslatedReference;
|
||||
|
||||
/**
|
||||
* The original label of the referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $originalLabel;
|
||||
|
||||
/**
|
||||
* The translated label of the referenced entity, used in assertions.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $translatedLabel;
|
||||
|
||||
/**
|
||||
* A user with permission to edit the referrer entity.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'node',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->labelOfNotTranslatedReference = $this->randomMachineName();
|
||||
$this->originalLabel = $this->randomMachineName();
|
||||
$this->translatedLabel = $this->randomMachineName();
|
||||
|
||||
$this->setUpLanguages();
|
||||
|
||||
// We setup languages, so we need to ensure that the language manager
|
||||
// and language path processor is updated.
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->setUpContentTypes();
|
||||
$this->enableTranslation();
|
||||
$this->setUpEntityReferenceField();
|
||||
$this->createContent();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'edit any ' . $this->referrerType->id() . ' content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the entity is displayed in an entity reference field.
|
||||
*/
|
||||
public function testEntityReferenceDisplay(): void {
|
||||
// Create a translated referrer entity.
|
||||
$this->referrerEntity = $this->createReferrerEntity();
|
||||
$this->assertEntityReferenceDisplay();
|
||||
$this->assertEntityReferenceFormDisplay();
|
||||
|
||||
// Disable translation for referrer content type.
|
||||
static::disableBundleTranslation('node', 'referrer');
|
||||
|
||||
// Create a referrer entity without translation.
|
||||
$this->referrerEntity = $this->createReferrerEntity(FALSE);
|
||||
$this->assertEntityReferenceDisplay();
|
||||
$this->assertEntityReferenceFormDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert entity reference display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntityReferenceDisplay(): void {
|
||||
$url = $this->referrerEntity->toUrl();
|
||||
$translation_url = $this->referrerEntity->toUrl('canonical', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains($this->labelOfNotTranslatedReference);
|
||||
$this->assertSession()->pageTextContains($this->originalLabel);
|
||||
$this->assertSession()->pageTextNotContains($this->translatedLabel);
|
||||
$this->drupalGet($translation_url);
|
||||
$this->assertSession()->pageTextContains($this->labelOfNotTranslatedReference);
|
||||
$this->assertSession()->pageTextNotContains($this->originalLabel);
|
||||
$this->assertSession()->pageTextContains($this->translatedLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert entity reference form display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntityReferenceFormDisplay(): void {
|
||||
$this->drupalLogin($this->webUser);
|
||||
$url = $this->referrerEntity->toUrl('edit-form');
|
||||
$translation_url = $this->referrerEntity->toUrl('edit-form', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[0][target_id]', $this->originalLabel . ' (1)');
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[1][target_id]', $this->labelOfNotTranslatedReference . ' (2)');
|
||||
$this->drupalGet($translation_url);
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[0][target_id]', $this->translatedLabel . ' (1)');
|
||||
$this->assertSession()->fieldValueEquals('test_reference_field[1][target_id]', $this->labelOfNotTranslatedReference . ' (2)');
|
||||
$this->drupalLogout();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds additional languages.
|
||||
*/
|
||||
protected function setUpLanguages(): void {
|
||||
static::createLanguageFromLangcode($this->translateToLangcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a test subject contents, with translation.
|
||||
*/
|
||||
protected function createContent(): void {
|
||||
$this->referencedEntityWithTranslation = $this->createReferencedEntityWithTranslation();
|
||||
$this->referencedEntityWithoutTranslation = $this->createNotTranslatedReferencedEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables translations where it needed.
|
||||
*/
|
||||
protected function enableTranslation(): void {
|
||||
// Enable translation for the entity types.
|
||||
$this->enableContentTranslation($this->testEntityTypeName, $this->referrerType->id());
|
||||
$this->enableContentTranslation($this->testEntityTypeName, $this->referencedType->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds term reference field for the article content type.
|
||||
*/
|
||||
protected function setUpEntityReferenceField(): void {
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->referenceFieldName,
|
||||
'entity_type' => $this->testEntityTypeName,
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
'translatable' => $this->translatable,
|
||||
'settings' => [
|
||||
'allowed_values' => [
|
||||
[
|
||||
'target_type' => $this->testEntityTypeName,
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $this->referenceFieldName,
|
||||
'bundle' => $this->referrerType->id(),
|
||||
'entity_type' => $this->testEntityTypeName,
|
||||
])
|
||||
->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay($this->testEntityTypeName, $this->referrerType->id())
|
||||
->setComponent($this->referenceFieldName, [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay($this->testEntityTypeName, $this->referrerType->id())
|
||||
->setComponent($this->referenceFieldName, [
|
||||
'type' => 'entity_reference_label',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create content types.
|
||||
*/
|
||||
protected function setUpContentTypes(): void {
|
||||
$this->referrerType = $this->drupalCreateContentType([
|
||||
'type' => 'referrer',
|
||||
'name' => 'Referrer',
|
||||
]);
|
||||
$this->referencedType = $this->drupalCreateContentType([
|
||||
'type' => 'referenced_page',
|
||||
'name' => 'Referenced Page',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a referenced entity with a translation.
|
||||
*/
|
||||
protected function createReferencedEntityWithTranslation() {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->originalLabel,
|
||||
'type' => $this->referencedType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
$node->save();
|
||||
$node->addTranslation($this->translateToLangcode, [
|
||||
'title' => $this->translatedLabel,
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the referenced entity.
|
||||
*/
|
||||
protected function createNotTranslatedReferencedEntity() {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->labelOfNotTranslatedReference,
|
||||
'type' => $this->referencedType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the referrer entity.
|
||||
*/
|
||||
protected function createReferrerEntity($translatable = TRUE) {
|
||||
/** @var \Drupal\node\Entity\Node $node */
|
||||
$node = \Drupal::entityTypeManager()->getStorage($this->testEntityTypeName)->create([
|
||||
'title' => $this->randomMachineName(),
|
||||
'type' => $this->referrerType->id(),
|
||||
'description' => [
|
||||
'value' => $this->randomMachineName(),
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
$this->referenceFieldName => [
|
||||
['target_id' => $this->referencedEntityWithTranslation->id()],
|
||||
['target_id' => $this->referencedEntityWithoutTranslation->id()],
|
||||
],
|
||||
'langcode' => $this->baseLangcode,
|
||||
]);
|
||||
if ($translatable) {
|
||||
$node->addTranslation($this->translateToLangcode, $node->toArray());
|
||||
}
|
||||
$node->save();
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests an autocomplete widget with file upload.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFileUploadTest extends BrowserTestBase {
|
||||
|
||||
use TestFileCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of a content type that will reference $referencedType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencingType;
|
||||
|
||||
/**
|
||||
* The name of a content type that will be referenced by $referencingType.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $referencedType;
|
||||
|
||||
/**
|
||||
* Node id.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $nodeId;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create "referencing" and "referenced" node types.
|
||||
$referencing = $this->drupalCreateContentType();
|
||||
$this->referencingType = $referencing->id();
|
||||
|
||||
$referenced = $this->drupalCreateContentType();
|
||||
$this->referencedType = $referenced->id();
|
||||
$this->nodeId = $this->drupalCreateNode(['type' => $referenced->id()])->id();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'translatable' => FALSE,
|
||||
'entity_types' => [],
|
||||
'settings' => [
|
||||
'target_type' => 'node',
|
||||
],
|
||||
'type' => 'entity_reference',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'label' => 'Entity reference field',
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
'required' => TRUE,
|
||||
'bundle' => $referencing->id(),
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
'handler_settings' => [
|
||||
// Reference a single vocabulary.
|
||||
'target_bundles' => [
|
||||
$referenced->id(),
|
||||
],
|
||||
],
|
||||
],
|
||||
])->save();
|
||||
|
||||
// Create a file field.
|
||||
$file_field_name = 'file_field';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $file_field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'file',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $referencing->id(),
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getViewDisplay('node', $referencing->id())
|
||||
->setComponent('test_field')
|
||||
->setComponent($file_field_name)
|
||||
->save();
|
||||
$display_repository->getFormDisplay('node', $referencing->id())
|
||||
->setComponent('test_field', [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->setComponent($file_field_name, [
|
||||
'type' => 'file_generic',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the autocomplete input element does not cause ajax fatal.
|
||||
*/
|
||||
public function testFileUpload(): void {
|
||||
$user1 = $this->drupalCreateUser([
|
||||
'access content',
|
||||
"create $this->referencingType content",
|
||||
]);
|
||||
$this->drupalLogin($user1);
|
||||
|
||||
$test_file = current($this->getTestFiles('text'));
|
||||
$edit['files[file_field_0]'] = \Drupal::service('file_system')->realpath($test_file->uri);
|
||||
$this->drupalGet('node/add/' . $this->referencingType);
|
||||
$this->submitForm($edit, 'Upload');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'test_field[0][target_id]' => $this->nodeId,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\config\Traits\AssertConfigEntityImportTrait;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests various Entity reference UI components.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceIntegrationTest extends BrowserTestBase {
|
||||
|
||||
use AssertConfigEntityImportTrait;
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* The entity type used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The bundle used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle = 'entity_test';
|
||||
|
||||
/**
|
||||
* The name of the field used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['config_test', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a test user.
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
'view test entity',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the entity reference field with all its supported field widgets.
|
||||
*/
|
||||
public function testSupportedEntityTypesAndWidgets(): void {
|
||||
foreach ($this->getTestEntities() as $key => $referenced_entities) {
|
||||
$this->fieldName = 'field_test_' . $referenced_entities[0]->getEntityTypeId();
|
||||
|
||||
// Create an Entity reference field.
|
||||
$this->createEntityReferenceField($this->entityType, $this->bundle, $this->fieldName, $this->fieldName, $referenced_entities[0]->getEntityTypeId(), 'default', [], 2);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Test the default 'entity_reference_autocomplete' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
$entity_name = $this->randomMachineName();
|
||||
$edit = [
|
||||
'name[0][value]' => $entity_name,
|
||||
$this->fieldName . '[0][target_id]' => $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')',
|
||||
// Test an input of the entity label without an ' (entity_id)' suffix.
|
||||
$this->fieldName . '[1][target_id]' => $referenced_entities[1]->label(),
|
||||
];
|
||||
$this->drupalGet($this->entityType . '/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Try to post the form again with no modification and check if the field
|
||||
// values remain the same.
|
||||
/** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($this->entityType);
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[0][target_id]', $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[1][target_id]', $referenced_entities[1]->label() . ' (' . $referenced_entities[1]->id() . ')');
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Test the 'entity_reference_autocomplete_tags' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'entity_reference_autocomplete_tags',
|
||||
])->save();
|
||||
|
||||
$entity_name = $this->randomMachineName();
|
||||
$target_id = $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')';
|
||||
// Test an input of the entity label without an ' (entity_id)' suffix.
|
||||
$target_id .= ', ' . $referenced_entities[1]->label();
|
||||
$edit = [
|
||||
'name[0][value]' => $entity_name,
|
||||
$this->fieldName . '[target_id]' => $target_id,
|
||||
];
|
||||
$this->drupalGet($this->entityType . '/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Try to post the form again with no modification and check if the field
|
||||
// values remain the same.
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($this->fieldName . '[target_id]', $target_id . ' (' . $referenced_entities[1]->id() . ')');
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_name, $referenced_entities);
|
||||
|
||||
// Test all the other widgets supported by the entity reference field.
|
||||
// Since we don't know the form structure for these widgets, just test
|
||||
// that editing and saving an already created entity works.
|
||||
$exclude = ['entity_reference_autocomplete', 'entity_reference_autocomplete_tags'];
|
||||
$entity = current($storage->loadByProperties(['name' => $entity_name]));
|
||||
$supported_widgets = \Drupal::service('plugin.manager.field.widget')->getOptions('entity_reference');
|
||||
$supported_widget_types = array_diff(array_keys($supported_widgets), $exclude);
|
||||
|
||||
foreach ($supported_widget_types as $widget_type) {
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => $widget_type,
|
||||
])->save();
|
||||
|
||||
$this->drupalGet($this->entityType . '/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm([], 'Save');
|
||||
// Some widget types do not guarantee that the order is kept, accept any
|
||||
// order.
|
||||
$this->assertFieldValues($entity_name, $referenced_entities, FALSE);
|
||||
}
|
||||
|
||||
// Reset to the default 'entity_reference_autocomplete' widget.
|
||||
$display_repository->getFormDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
// Set first entity as the default_value.
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
'default_value_input[' . $this->fieldName . '][0][target_id]' => $referenced_entities[0]->label() . ' (' . $referenced_entities[0]->id() . ')',
|
||||
];
|
||||
if ($key == 'content') {
|
||||
$field_edit['settings[handler_settings][target_bundles][' . $referenced_entities[0]->getEntityTypeId() . ']'] = TRUE;
|
||||
}
|
||||
$this->drupalGet($this->entityType . '/structure/' . $this->bundle . '/fields/' . $this->entityType . '.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
// Ensure the configuration has the expected dependency on the entity that
|
||||
// is being used a default value.
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$this->assertContains($referenced_entities[0]->getConfigDependencyName(), $field->getDependencies()[$key], 'Expected ' . $key . ' dependency ' . $referenced_entities[0]->getConfigDependencyName() . ' found');
|
||||
// Ensure that the field can be imported without change even after the
|
||||
// default value deleted.
|
||||
$referenced_entities[0]->delete();
|
||||
// Reload the field since deleting the default value can change the field.
|
||||
\Drupal::entityTypeManager()->getStorage($field->getEntityTypeId())->resetCache([$field->id()]);
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$this->assertConfigEntityImport($field);
|
||||
|
||||
// Once the default value has been removed after saving the dependency
|
||||
// should be removed.
|
||||
$field = FieldConfig::loadByName($this->entityType, $this->bundle, $this->fieldName);
|
||||
$field->save();
|
||||
$dependencies = $field->getDependencies();
|
||||
$this->assertFalse(isset($dependencies[$key]) && in_array($referenced_entities[0]->getConfigDependencyName(), $dependencies[$key]), $key . ' dependency ' . $referenced_entities[0]->getConfigDependencyName() . ' does not exist.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the reference field values are correct.
|
||||
*
|
||||
* @param string $entity_name
|
||||
* The name of the test entity.
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $referenced_entities
|
||||
* An array of referenced entities.
|
||||
* @param bool $assert_order
|
||||
* Whether the correct order of the references should be assert or just
|
||||
* that they exist.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldValues(string $entity_name, array $referenced_entities, $assert_order = TRUE): void {
|
||||
$entity = current($this->container->get('entity_type.manager')->getStorage(
|
||||
$this->entityType)->loadByProperties(['name' => $entity_name]));
|
||||
|
||||
$this->assertNotEmpty($entity, "$this->entityType: Entity found in the database.");
|
||||
|
||||
if ($assert_order) {
|
||||
$this->assertEquals($referenced_entities[0]->id(), $entity->{$this->fieldName}->target_id);
|
||||
$this->assertEquals($referenced_entities[0]->id(), $entity->{$this->fieldName}->entity->id());
|
||||
$this->assertEquals($referenced_entities[0]->label(), $entity->{$this->fieldName}->entity->label());
|
||||
|
||||
$this->assertEquals($referenced_entities[1]->id(), $entity->{$this->fieldName}[1]->target_id);
|
||||
$this->assertEquals($referenced_entities[1]->id(), $entity->{$this->fieldName}[1]->entity->id());
|
||||
$this->assertEquals($referenced_entities[1]->label(), $entity->{$this->fieldName}[1]->entity->label());
|
||||
}
|
||||
else {
|
||||
$ids = [$referenced_entities[0]->id(), $referenced_entities[1]->id()];
|
||||
$labels = [$referenced_entities[0]->label(), $referenced_entities[1]->label()];
|
||||
$this->assertContains($entity->{$this->fieldName}->target_id, $ids);
|
||||
$this->assertContains($entity->{$this->fieldName}->entity->label(), $labels);
|
||||
$this->assertContains($entity->{$this->fieldName}[1]->target_id, $ids);
|
||||
$this->assertContains($entity->{$this->fieldName}[1]->entity->label(), $labels);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates two content and two config test entities.
|
||||
*
|
||||
* @return array
|
||||
* An array of entity objects.
|
||||
*/
|
||||
protected function getTestEntities(): array {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('config_test');
|
||||
$config_entity_1 = $storage->create(['id' => $this->randomMachineName(), 'label' => $this->randomMachineName()]);
|
||||
$config_entity_1->save();
|
||||
$config_entity_2 = $storage->create(['id' => $this->randomMachineName(), 'label' => $this->randomMachineName()]);
|
||||
$config_entity_2->save();
|
||||
|
||||
$content_entity_1 = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$content_entity_1->save();
|
||||
$content_entity_2 = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$content_entity_2->save();
|
||||
|
||||
return [
|
||||
'config' => [
|
||||
$config_entity_1,
|
||||
$config_entity_2,
|
||||
],
|
||||
'content' => [
|
||||
$content_entity_1,
|
||||
$content_entity_2,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests possible XSS security issues in entity references.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceXSSTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests markup is escaped in the entity reference select and label formatter.
|
||||
*/
|
||||
public function testEntityReferenceXSS(): void {
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
|
||||
// Create a node with markup in the title.
|
||||
$node_type_one = $this->drupalCreateContentType();
|
||||
$node = [
|
||||
'type' => $node_type_one->id(),
|
||||
'title' => '<em>I am kitten</em>',
|
||||
];
|
||||
$referenced_node = $this->drupalCreateNode($node);
|
||||
|
||||
$node_type_two = $this->drupalCreateContentType(['name' => '<em>bundle with markup</em>']);
|
||||
$this->drupalCreateNode([
|
||||
'type' => $node_type_two->id(),
|
||||
'title' => 'My bundle has markup',
|
||||
]);
|
||||
|
||||
$this->createEntityReferenceField('node', 'article', 'entity_reference_test', 'Entity Reference test', 'node', 'default', ['target_bundles' => [$node_type_one->id(), $node_type_two->id()]]);
|
||||
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_select'])
|
||||
->save();
|
||||
EntityViewDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'entity_reference_label'])
|
||||
->save();
|
||||
|
||||
// Create a node and reference the node with markup in the title.
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'create article content',
|
||||
]));
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
$this->assertSession()->assertEscaped($node_type_two->label());
|
||||
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomString(),
|
||||
'entity_reference_test' => $referenced_node->id(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
|
||||
// Test the options_buttons type.
|
||||
EntityFormDisplay::load('node.article.default')
|
||||
->setComponent('entity_reference_test', ['type' => 'options_buttons'])
|
||||
->save();
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->assertEscaped($referenced_node->getTitle());
|
||||
// options_buttons does not support optgroups.
|
||||
$this->assertSession()->pageTextNotContains('bundle with markup');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\EntityReference\Views;
|
||||
|
||||
use Drupal\Component\Serialization\Json;
|
||||
use Drupal\Component\Utility\Crypt;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests entity reference selection handler.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class SelectionTest extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'views',
|
||||
'entity_reference_test',
|
||||
'entity_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of node titles, keyed by content type and node ID.
|
||||
*
|
||||
* @var \Drupal\node\NodeInterface[]
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* The handler settings for the entity reference field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $handlerSettings;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create content types and nodes.
|
||||
$type1 = $this->drupalCreateContentType()->id();
|
||||
$type2 = $this->drupalCreateContentType()->id();
|
||||
// Add some characters that should be escaped but not double escaped.
|
||||
$node1 = $this->drupalCreateNode(['type' => $type1, 'title' => 'Test first node &<>']);
|
||||
$node2 = $this->drupalCreateNode(['type' => $type1, 'title' => 'Test second node &&&']);
|
||||
$node3 = $this->drupalCreateNode(['type' => $type2, 'title' => 'Test third node <span />']);
|
||||
|
||||
foreach ([$node1, $node2, $node3] as $node) {
|
||||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Ensure the bundle to which the field is attached actually exists, or we
|
||||
// will get config validation errors.
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
// Create an entity reference field.
|
||||
$handler_settings = [
|
||||
'view' => [
|
||||
'view_name' => 'test_entity_reference',
|
||||
'display_name' => 'entity_reference_1',
|
||||
],
|
||||
];
|
||||
$this->handlerSettings = $handler_settings;
|
||||
$this->createEntityReferenceField('entity_test', 'test_bundle', 'test_field', $this->randomString(), 'node', 'views', $handler_settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the Views selection handles the views output properly.
|
||||
*/
|
||||
public function testAutocompleteOutput(): void {
|
||||
// Reset any internal static caching.
|
||||
\Drupal::service('entity_type.manager')->getStorage('node')->resetCache();
|
||||
|
||||
$view = Views::getView('test_entity_reference');
|
||||
$view->setDisplay();
|
||||
|
||||
// Enable the display of the 'type' field so we can test that the output
|
||||
// does not contain only the entity label.
|
||||
$fields = $view->displayHandlers->get('entity_reference_1')->getOption('fields');
|
||||
$fields['type']['exclude'] = FALSE;
|
||||
$view->displayHandlers->get('entity_reference_1')->setOption('fields', $fields);
|
||||
$view->save();
|
||||
|
||||
// Prepare the selection settings key needed by the entity reference
|
||||
// autocomplete route.
|
||||
$target_type = 'node';
|
||||
$selection_handler = 'views';
|
||||
$selection_settings = $this->handlerSettings;
|
||||
$selection_settings_key = Crypt::hmacBase64(serialize($selection_settings) . $target_type . $selection_handler, Settings::getHashSalt());
|
||||
\Drupal::keyValue('entity_autocomplete')->set($selection_settings_key, $selection_settings);
|
||||
|
||||
$result = Json::decode($this->drupalGet('entity_reference_autocomplete/' . $target_type . '/' . $selection_handler . '/' . $selection_settings_key, ['query' => ['q' => 't']]));
|
||||
|
||||
$expected = [
|
||||
0 => [
|
||||
'value' => $this->nodes[1]->bundle() . ': ' . $this->nodes[1]->label() . ' (' . $this->nodes[1]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[1]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[1]->label()) . '</span></span>',
|
||||
],
|
||||
1 => [
|
||||
'value' => $this->nodes[2]->bundle() . ': ' . $this->nodes[2]->label() . ' (' . $this->nodes[2]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[2]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[2]->label()) . '</span></span>',
|
||||
],
|
||||
2 => [
|
||||
'value' => $this->nodes[3]->bundle() . ': ' . $this->nodes[3]->label() . ' (' . $this->nodes[3]->id() . ')',
|
||||
'label' => '<span class="views-field views-field-type"><span class="field-content">' . $this->nodes[3]->bundle() . '</span></span>: <span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[3]->label()) . '</span></span>',
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected, $result, 'The autocomplete result of the Views entity reference selection handler contains the proper output.');
|
||||
}
|
||||
|
||||
}
|
||||
101
web/core/modules/field/tests/src/Functional/FieldAccessTest.php
Normal file
101
web/core/modules/field/tests/src/Functional/FieldAccessTest.php
Normal file
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests Field access.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldAccessTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Node entity to use in this test.
|
||||
*
|
||||
* @var \Drupal\node\Entity\Node
|
||||
*/
|
||||
protected $node;
|
||||
|
||||
/**
|
||||
* Field value to test display on nodes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testViewFieldValue;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser(['view test_view_field content']);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Create content type.
|
||||
$content_type_info = $this->drupalCreateContentType();
|
||||
$content_type = $content_type_info->id();
|
||||
|
||||
$field_storage = [
|
||||
'field_name' => 'test_view_field',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$field = [
|
||||
'field_name' => $field_storage['field_name'],
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $content_type,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
// Assign display properties for the 'default' and 'teaser' view modes.
|
||||
foreach (['default', 'teaser'] as $view_mode) {
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('node', $content_type, $view_mode)
|
||||
->setComponent($field_storage['field_name'])
|
||||
->save();
|
||||
}
|
||||
|
||||
// Create test node.
|
||||
$this->testViewFieldValue = 'This is some text';
|
||||
$settings = [];
|
||||
$settings['type'] = $content_type;
|
||||
$settings['title'] = 'Field view access test';
|
||||
$settings['test_view_field'] = [['value' => $this->testViewFieldValue]];
|
||||
$this->node = $this->drupalCreateNode($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that hook_entity_field_access() is called.
|
||||
*/
|
||||
public function testFieldAccess(): void {
|
||||
|
||||
// Assert the text is visible.
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
$this->assertSession()->pageTextContains($this->testViewFieldValue);
|
||||
|
||||
// Assert the text is not visible for anonymous users.
|
||||
// The field_test module implements hook_entity_field_access() which will
|
||||
// specifically target the 'test_view_field' field.
|
||||
$this->drupalLogout();
|
||||
$this->drupalGet('node/' . $this->node->id());
|
||||
$this->assertSession()->pageTextNotContains($this->testViewFieldValue);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the default value callback.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldDefaultValueCallbackTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldName = 'field_test';
|
||||
|
||||
// Create Article node types.
|
||||
if ($this->profile != 'standard') {
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
}
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer node fields',
|
||||
]));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the default value callback functionality for fields.
|
||||
*/
|
||||
public function testDefaultValueCallbackForm(): void {
|
||||
// Create a field and storage for checking.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
])->save();
|
||||
/** @var \Drupal\field\Entity\FieldConfig $field_config */
|
||||
$field_config = FieldConfig::create([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => $this->fieldName,
|
||||
'bundle' => 'article',
|
||||
]);
|
||||
$field_config->save();
|
||||
|
||||
// Check that the default field form is visible when no callback is set.
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', '');
|
||||
|
||||
// Set a different field value, it should be on the field.
|
||||
$default_value = $this->randomString();
|
||||
$field_config->setDefaultValue([['value' => $default_value]])->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', $default_value);
|
||||
|
||||
// Set a different field value to the field directly, instead of an array.
|
||||
$default_value = $this->randomString();
|
||||
$field_config->setDefaultValue($default_value)->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldValueEquals('default_value_input[field_test][0][value]', $default_value);
|
||||
|
||||
// Set a default value callback instead, and the default field form should
|
||||
// not be visible.
|
||||
$field_config->setDefaultValueCallback('\Drupal\field_test\FieldDefaultValueCallbackProvider::calculateDefaultValue')->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_test');
|
||||
$this->assertSession()->fieldNotExists('default_value_input[field_test][0][value]');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests help display for the Field module.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldHelpTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'help'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The admin user that will be created.
|
||||
*
|
||||
* @var \Drupal\user\Entity\User|false
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create the admin user.
|
||||
$this->adminUser = $this->drupalCreateUser([
|
||||
'access help pages',
|
||||
'view the administration theme',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Field module's help page.
|
||||
*/
|
||||
public function testFieldHelp(): void {
|
||||
// Log in the admin user.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Visit the Help page and make sure no warnings or notices are thrown.
|
||||
$this->drupalGet('admin/help/field');
|
||||
|
||||
// Enable the Options, Email and Field API Test modules.
|
||||
\Drupal::service('module_installer')->install(['options', 'field_test']);
|
||||
|
||||
$this->drupalGet('admin/help/field');
|
||||
$this->assertSession()->linkExists('Options', 0, 'Options module is listed on the Field help page.');
|
||||
// Verify that modules with field types that do not implement hook_help are
|
||||
// listed.
|
||||
$this->assertSession()->pageTextContains('Field API Test');
|
||||
$this->assertSession()->linkNotExists('Field API Test', 'Modules with field types that do not implement hook_help are not linked.');
|
||||
$this->assertSession()->linkNotExists('Link', 'Modules that have not been installed, are not listed.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests deleting field storage when a module in uninstalled through the UI.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\field\ConfigImporterFieldPurger
|
||||
* @see field_config_import_steps_alter()
|
||||
* @see field_form_config_admin_import_form_alter()
|
||||
*/
|
||||
class FieldImportDeleteUninstallUiTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'telephone',
|
||||
'config',
|
||||
'filter',
|
||||
'datetime',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser(['synchronize configuration']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting field storages and fields as part of config import.
|
||||
*/
|
||||
public function testImportDeleteUninstall(): void {
|
||||
// Create a telephone field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_tel',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a text field.
|
||||
$date_field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_date',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'datetime',
|
||||
]);
|
||||
$date_field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $date_field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create an entity which has values for the telephone and text field.
|
||||
$entity = EntityTest::create();
|
||||
$value = '+0123456789';
|
||||
$entity->field_tel = $value;
|
||||
$entity->field_date = time();
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Delete the text field before exporting configuration so that we can test
|
||||
// that deleted fields that are provided by modules that will be uninstalled
|
||||
// are also purged and that the UI message includes such fields.
|
||||
$date_field_storage->delete();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->field_tel->value);
|
||||
$this->assertEquals($value, $entity->field_tel[0]->value);
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Stage uninstall of the Telephone module.
|
||||
$core_extension = $this->config('core.extension')->get();
|
||||
unset($core_extension['module']['telephone']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
// Stage the field deletion including its dependencies.
|
||||
$sync->delete('field.storage.entity_test.field_tel');
|
||||
$sync->delete('field.field.entity_test.entity_test.field_tel');
|
||||
$sync->delete('core.entity_form_display.entity_test.entity_test.default');
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
// Test that the message for one field being purged during a configuration
|
||||
// synchronization is correct.
|
||||
$this->assertSession()->pageTextContains('This synchronization will delete data from the field entity_test.field_tel.');
|
||||
|
||||
// Stage an uninstall of the datetime module to test the message for
|
||||
// multiple fields.
|
||||
unset($core_extension['module']['datetime']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$this->assertSession()->pageTextContains('This synchronization will delete data from the fields: entity_test.field_tel, entity_test.field_date.');
|
||||
|
||||
// This will purge all the data, delete the field and uninstall the
|
||||
// Telephone and Text modules.
|
||||
$this->submitForm([], 'Import all');
|
||||
$this->assertSession()->pageTextNotContains('Field data will be deleted by this synchronization.');
|
||||
$this->rebuildContainer();
|
||||
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
|
||||
$this->assertNull(\Drupal::service('entity.repository')->loadEntityByUuid('field_storage_config', $field_storage->uuid()), 'The telephone field has been deleted by the configuration synchronization');
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Text field has been completed removed from the system.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the synchronization form is available when the core.extension.yml is missing.
|
||||
*/
|
||||
public function testSynchronizeForm(): void {
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($this->container->get('config.storage'), $sync);
|
||||
|
||||
$sync->delete('core.extension');
|
||||
$this->drupalGet('admin/config/development/configuration');
|
||||
$assertSession = $this->assertSession();
|
||||
$this->assertSession()->elementExists('css', 'input[value="Import all"]')->click();
|
||||
$assertSession->pageTextContains('The core.extension configuration does not exist.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Parent class for Field API tests.
|
||||
*/
|
||||
abstract class FieldTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Generate random values for a field_test field.
|
||||
*
|
||||
* @param int $cardinality
|
||||
* Number of values to generate.
|
||||
*
|
||||
* @return array
|
||||
* An array of random values, in the format expected for field values.
|
||||
*/
|
||||
public function _generateTestFieldValues($cardinality) {
|
||||
$values = [];
|
||||
for ($i = 0; $i < $cardinality; $i++) {
|
||||
// field_test fields treat 0 as 'empty value'.
|
||||
$values[$i]['value'] = mt_rand(1, 127);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a field has the expected values in an entity.
|
||||
*
|
||||
* This function only checks a single column in the field values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to test.
|
||||
* @param string $field_name
|
||||
* The name of the field to test.
|
||||
* @param array $expected_values
|
||||
* The array of expected values.
|
||||
* @param string $langcode
|
||||
* (Optional) The language code for the values. Defaults to
|
||||
* \Drupal\Core\Language\LanguageInterface::LANGCODE_DEFAULT.
|
||||
* @param string $column
|
||||
* (Optional) The name of the column to check. Defaults to 'value'.
|
||||
*/
|
||||
public function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_DEFAULT, $column = 'value') {
|
||||
// Re-load the entity to make sure we have the latest changes.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity->getEntityTypeId());
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$e = $storage->load($entity->id());
|
||||
|
||||
$field = $values = $e->getTranslation($langcode)->$field_name;
|
||||
// Filter out empty values so that they don't mess with the assertions.
|
||||
$field->filterEmptyItems();
|
||||
$values = $field->getValue();
|
||||
$this->assertSameSize($expected_values, $values, 'Expected number of values were saved.');
|
||||
foreach ($expected_values as $key => $value) {
|
||||
$this->assertEquals($value, $values[$key][$column], "Value $value was saved correctly.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
642
web/core/modules/field/tests/src/Functional/FormTest.php
Normal file
642
web/core/modules/field/tests/src/Functional/FormTest.php
Normal file
@ -0,0 +1,642 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestBaseFieldDisplay;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field form handling.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Locale is installed so that TranslatableMarkup actually does something.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_test',
|
||||
'options',
|
||||
'entity_test',
|
||||
'locale',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of values defining a field single.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageSingle;
|
||||
|
||||
/**
|
||||
* An array of values defining a field with unlimited cardinality.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageUnlimited;
|
||||
|
||||
/**
|
||||
* An array of values defining a field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageSingle = [
|
||||
'field_name' => 'field_single',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$this->fieldStorageUnlimited = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the single-value field form functionality.
|
||||
*/
|
||||
public function testFieldFormSingle(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
// Create token value expected for description.
|
||||
$token_description = Html::escape($this->config('system.site')->get('name')) . '_description';
|
||||
$this->assertSession()->pageTextContains($token_description);
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Check that hook_field_widget_single_element_form_alter() does not believe
|
||||
// this is the default value form.
|
||||
$this->assertSession()->pageTextNotContains('From hook_field_widget_single_element_form_alter(): Default form is true.');
|
||||
// Check that hook_field_widget_single_element_form_alter() does not believe
|
||||
// this is the default value form.
|
||||
$this->assertSession()->pageTextNotContains('From hook_field_widget_complete_form_alter(): Default form is true.');
|
||||
|
||||
// Submit with invalid value (field-level validation).
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => -1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} does not accept the value -1.");
|
||||
// @todo check that the correct field is flagged for error.
|
||||
|
||||
// Create an entity
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was saved');
|
||||
|
||||
// Display edit form.
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
// Check that the widget is displayed with the correct default value.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", $value);
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Update the entity.
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been updated.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was updated');
|
||||
|
||||
// Empty the field.
|
||||
$value = '';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been updated.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field was emptied');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field widget default values on entity forms.
|
||||
*/
|
||||
public function testFieldFormDefaultValue(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$default = rand(1, 127);
|
||||
$this->field['default_value'] = [['value' => $default]];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Test that the default value is displayed correctly.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", $default);
|
||||
|
||||
// Try to submit an empty value.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => '',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertTrue($entity->{$field_name}->isEmpty(), 'Field is now empty.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the required single-value field form.
|
||||
*/
|
||||
public function testFieldFormSingleRequired(): void {
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['required'] = TRUE;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Submit with missing required value.
|
||||
$edit = [];
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} field is required.");
|
||||
|
||||
// Create an entity
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was saved');
|
||||
|
||||
// Edit with missing required value.
|
||||
$value = '';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$this->field['label']} field is required.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the unlimited-value field form.
|
||||
*/
|
||||
public function testFieldFormUnlimited(): void {
|
||||
$field_storage = $this->fieldStorageUnlimited;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Verify that only one "Default value" field
|
||||
// exists on the Manage field display.
|
||||
$this->drupalGet("entity_test/structure/entity_test/fields/entity_test.entity_test.{$field_name}");
|
||||
$this->assertSession()->elementsCount('xpath', "//table[@id='field-unlimited-values']/tbody/tr//input[contains(@class, 'form-text')]", 1);
|
||||
|
||||
// Display creation form -> 1 widget.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[1][value]");
|
||||
|
||||
// Check if aria-describedby attribute is placed on multiple value widgets.
|
||||
$this->assertSession()->elementAttributeContains('xpath', '//table[@id="field-unlimited-values"]', 'aria-describedby', 'edit-field-unlimited--description');
|
||||
|
||||
// Press 'add more' button -> 2 widgets.
|
||||
$this->submitForm([], 'Add another item');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[1][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[2][value]");
|
||||
// @todo check that non-field inputs are preserved ('title'), etc.
|
||||
|
||||
// Yet another time so that we can play with more values -> 3 widgets.
|
||||
$this->submitForm([], 'Add another item');
|
||||
|
||||
// Prepare values and weights.
|
||||
$count = 3;
|
||||
$delta_range = $count - 1;
|
||||
$values = $weights = $pattern = $expected_values = [];
|
||||
$edit = [];
|
||||
for ($delta = 0; $delta <= $delta_range; $delta++) {
|
||||
// Assign unique random values and weights.
|
||||
do {
|
||||
$value = mt_rand(1, 127);
|
||||
} while (in_array($value, $values));
|
||||
do {
|
||||
$weight = mt_rand(-$delta_range, $delta_range);
|
||||
} while (in_array($weight, $weights));
|
||||
$edit["{$field_name}[$delta][value]"] = $value;
|
||||
$edit["{$field_name}[$delta][_weight]"] = $weight;
|
||||
// We'll need three slightly different formats to check the values.
|
||||
$values[$delta] = $value;
|
||||
$weights[$delta] = $weight;
|
||||
$field_values[$weight]['value'] = (string) $value;
|
||||
$pattern[$weight] = "<input [^>]*value=\"$value\" [^>]*";
|
||||
}
|
||||
|
||||
// Press 'add more' button -> 4 widgets
|
||||
$this->submitForm($edit, 'Add another item');
|
||||
for ($delta = 0; $delta <= $delta_range; $delta++) {
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][value]", $values[$delta]);
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][_weight]", $weights[$delta]);
|
||||
}
|
||||
ksort($pattern);
|
||||
$pattern = implode('.*', array_values($pattern));
|
||||
// Verify that the widgets are displayed in the correct order.
|
||||
$this->assertSession()->responseMatches("|$pattern|s");
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[$delta][_weight]", $delta);
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[" . ($delta + 1) . '][value]');
|
||||
|
||||
// Submit the form and create the entity.
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$entity = EntityTest::load($id);
|
||||
ksort($field_values);
|
||||
$field_values = array_values($field_values);
|
||||
$this->assertSame($field_values, $entity->{$field_name}->getValue(), 'Field values were saved in the correct order');
|
||||
|
||||
// Display edit form: check that the expected number of widgets is
|
||||
// displayed, with correct values change values, reorder, leave an empty
|
||||
// value in the middle.
|
||||
// Submit: check that the entity is updated with correct values
|
||||
// Re-submit: check that the field can be emptied.
|
||||
|
||||
// Test with several multiple fields in a form
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the position of the required label.
|
||||
*/
|
||||
public function testFieldFormUnlimitedRequired(): void {
|
||||
$field_name = $this->fieldStorageUnlimited['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['required'] = TRUE;
|
||||
FieldStorageConfig::create($this->fieldStorageUnlimited)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form -> 1 widget.
|
||||
$this->drupalGet('entity_test/add');
|
||||
// Check that the Required symbol is present for the label of the field
|
||||
// with unlimited cardinality.
|
||||
$this->assertSession()->elementAttributeContains('xpath', "//h4[contains(@class, 'label') and contains(text(), '{$this->field['label']}')]", 'class', 'js-form-required');
|
||||
// Check that the label of the field input is visually hidden and contains
|
||||
// the field title and an indication of the delta for a11y.
|
||||
$this->assertSession()->elementExists('xpath', "//label[@for='edit-field-unlimited-0-value' and contains(@class, 'visually-hidden') and contains(text(), '{$this->field['label']} (value 1)')]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget handling of multiple required radios.
|
||||
*/
|
||||
public function testFieldFormMultivalueWithRequiredRadio(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Create a multivalue test field.
|
||||
$field_storage = $this->fieldStorageUnlimited;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Add a required radio field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'required_radio_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'list_string',
|
||||
'settings' => [
|
||||
'allowed_values' => ['yes' => 'yes', 'no' => 'no'],
|
||||
],
|
||||
])->save();
|
||||
$field = [
|
||||
'field_name' => 'required_radio_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'required' => TRUE,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
$display_repository->getFormDisplay($field['entity_type'], $field['bundle'])
|
||||
->setComponent($field['field_name'], [
|
||||
'type' => 'options_buttons',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
// Press the 'Add more' button.
|
||||
$this->submitForm([], 'Add another item');
|
||||
|
||||
// Verify that no error is thrown by the radio element.
|
||||
$this->assertSession()->elementNotExists('xpath', '//div[contains(@class, "error")]');
|
||||
|
||||
// Verify that the widget is added.
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[1][value]", '');
|
||||
// Verify that no extraneous widget is displayed.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[2][value]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests fields with no 'edit' access.
|
||||
*/
|
||||
public function testFieldFormAccess(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$entity_type = 'entity_test_rev';
|
||||
// Create a "regular" field.
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_storage['entity_type'] = $entity_type;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$field = $this->field;
|
||||
$field['field_name'] = $field_name;
|
||||
$field['entity_type'] = $entity_type;
|
||||
$field['bundle'] = $entity_type;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($field)->save();
|
||||
$display_repository->getFormDisplay($entity_type, $entity_type)
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Create a field with no edit access. See
|
||||
// field_test_entity_field_access().
|
||||
$field_storage_no_access = [
|
||||
'field_name' => 'field_no_edit_access',
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$field_name_no_access = $field_storage_no_access['field_name'];
|
||||
$field_no_access = [
|
||||
'field_name' => $field_name_no_access,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $entity_type,
|
||||
'default_value' => [0 => ['value' => 99]],
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_no_access)->save();
|
||||
FieldConfig::create($field_no_access)->save();
|
||||
$display_repository->getFormDisplay($field_no_access['entity_type'], $field_no_access['bundle'])
|
||||
->setComponent($field_name_no_access)
|
||||
->save();
|
||||
|
||||
// Test that the form structure includes full information for each delta
|
||||
// apart from #access.
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => 0, 'revision_id' => 0]);
|
||||
|
||||
$display = $display_repository->getFormDisplay($entity_type, $entity_type);
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$display->buildForm($entity, $form, $form_state);
|
||||
|
||||
$this->assertFalse($form[$field_name_no_access]['#access'], 'Field #access is FALSE for the field without edit access.');
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet($entity_type . '/add');
|
||||
// Check that the widget is not displayed if field access is denied.
|
||||
$this->assertSession()->fieldNotExists("{$field_name_no_access}[0][value]");
|
||||
|
||||
// Create entity.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match("|$entity_type/manage/(\d+)|", $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
|
||||
// Check that the default value was saved.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'Default value was saved for the field with no edit access.');
|
||||
$this->assertEquals(1, $entity->{$field_name}->value, 'Entered value vas saved for the field with edit access.');
|
||||
|
||||
// Create a new revision.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 2,
|
||||
'revision' => TRUE,
|
||||
];
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the new revision has the expected values.
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'New revision has the expected value for the field with no edit access.');
|
||||
$this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
|
||||
|
||||
// Check that the revision is also saved in the revisions table.
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
$entity = $storage->loadRevision($entity->getRevisionId());
|
||||
$this->assertEquals(99, $entity->{$field_name_no_access}->value, 'New revision has the expected value for the field with no edit access.');
|
||||
$this->assertEquals(2, $entity->{$field_name}->value, 'New revision has the expected value for the field with edit access.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hiding a field in a form.
|
||||
*/
|
||||
public function testHiddenField(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$field_storage = $this->fieldStorageSingle;
|
||||
$field_storage['entity_type'] = $entity_type;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
$this->field['default_value'] = [0 => ['value' => 99]];
|
||||
$this->field['entity_type'] = $entity_type;
|
||||
$this->field['bundle'] = $entity_type;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$this->field = FieldConfig::create($this->field);
|
||||
$this->field->save();
|
||||
// We explicitly do not assign a widget in a form display, so the field
|
||||
// stays hidden in forms.
|
||||
|
||||
// Display the entity creation form.
|
||||
$this->drupalGet($entity_type . '/add');
|
||||
|
||||
// Create an entity and test that the default value is assigned correctly to
|
||||
// the field that uses the hidden widget.
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[0][value]");
|
||||
$this->submitForm([], 'Save');
|
||||
preg_match('|' . $entity_type . '/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test_rev ' . $id . ' has been created.');
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals(99, $entity->{$field_name}->value, 'Default value was saved');
|
||||
|
||||
// Update the field to remove the default value, and switch to the default
|
||||
// widget.
|
||||
$this->field->setDefaultValue([]);
|
||||
$this->field->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->field->getTargetBundle())
|
||||
->setComponent($this->field->getName(), [
|
||||
'type' => 'test_field_widget',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display edit form.
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", 99);
|
||||
|
||||
// Update the entity.
|
||||
$value = mt_rand(1, 127);
|
||||
$edit = ["{$field_name}[0][value]" => $value];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('entity_test_rev ' . $id . ' has been updated.');
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'Field value was updated');
|
||||
|
||||
// Set the field back to hidden.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->field->getTargetBundle())
|
||||
->removeComponent($this->field->getName())
|
||||
->save();
|
||||
|
||||
// Create a new revision.
|
||||
$edit = ['revision' => TRUE];
|
||||
$this->drupalGet($entity_type . '/manage/' . $id . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the expected value has been carried over to the new revision.
|
||||
$storage->resetCache([$id]);
|
||||
$entity = $storage->load($id);
|
||||
$this->assertEquals($value, $entity->{$field_name}->value, 'New revision has the expected value for the field with the Hidden widget');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form display of the label for multi-value fields.
|
||||
*/
|
||||
public function testLabelOnMultiValueFields(): void {
|
||||
$user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Ensure that the 'bar' bundle exists, to avoid config validation errors.
|
||||
EntityTestHelper::createBundle('bar', entity_type: 'entity_test_base_field_display');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'field_name' => 'foo',
|
||||
'type' => 'text',
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'field_name' => 'foo',
|
||||
// Set a dangerous label to test XSS filtering.
|
||||
'label' => "<script>alert('a configurable field');</script>",
|
||||
])->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'mode' => 'default',
|
||||
])->setComponent('foo', ['type' => 'text_textfield'])->enable()->save();
|
||||
|
||||
$entity = EntityTestBaseFieldDisplay::create(['type' => 'bar']);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet('entity_test_base_field_display/manage/' . $entity->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('A field with multiple values');
|
||||
// Test if labels were XSS filtered.
|
||||
$this->assertSession()->assertEscaped("<script>alert('a configurable field');</script>");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\FunctionalString;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the creation of string fields.
|
||||
*
|
||||
* @group text
|
||||
*/
|
||||
class StringFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user without any special permissions.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $webUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->webUser = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'access content',
|
||||
]);
|
||||
$this->drupalLogin($this->webUser);
|
||||
}
|
||||
|
||||
// Test fields.
|
||||
|
||||
/**
|
||||
* Tests widgets.
|
||||
*/
|
||||
public function testTextfieldWidgets(): void {
|
||||
$this->_testTextfieldWidgets('string', 'string_textfield');
|
||||
$this->_testTextfieldWidgets('string_long', 'string_textarea');
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for testTextfieldWidgets().
|
||||
*/
|
||||
public function _testTextfieldWidgets($field_type, $widget_type): void {
|
||||
// Create a field.
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => $field_type,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => $widget_type,
|
||||
'settings' => [
|
||||
'placeholder' => 'A placeholder on ' . $widget_type,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->fieldNotExists("{$field_name}[0][format]");
|
||||
$this->assertSession()->responseContains('placeholder="A placeholder on ' . $widget_type . '"');
|
||||
|
||||
// Submit with some value.
|
||||
$value = $this->randomMachineName();
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Display the entity.
|
||||
$entity = EntityTest::load($id);
|
||||
$display = $display_repository->getViewDisplay($entity->getEntityTypeId(), $entity->bundle(), 'full');
|
||||
$content = $display->build($entity);
|
||||
$rendered_entity = \Drupal::service('renderer')->renderRoot($content);
|
||||
$this->assertStringContainsString($value, (string) $rendered_entity);
|
||||
}
|
||||
|
||||
}
|
||||
14
web/core/modules/field/tests/src/Functional/GenericTest.php
Normal file
14
web/core/modules/field/tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,246 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestBaseFieldDisplay;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field form handling.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MultipleWidgetFormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Locale is installed so that TranslatableMarkup actually does something.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_test',
|
||||
'options',
|
||||
'entity_test',
|
||||
'locale',
|
||||
'field_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* An array of values defining a field multiple.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageMultiple;
|
||||
|
||||
/**
|
||||
* An array of values defining a field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageMultiple = [
|
||||
'field_name' => 'field_multiple',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widgets handling multiple values.
|
||||
*/
|
||||
public function testFieldFormMultipleWidget(): void {
|
||||
// Create a field with fixed cardinality, configure the form to use a
|
||||
// "multiple" widget.
|
||||
$field_storage = $this->fieldStorageMultiple;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
$form = \Drupal::service('entity_display.repository')->getFormDisplay($this->field['entity_type'], $this->field['bundle'], 'default')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'test_field_widget_multiple',
|
||||
]);
|
||||
$form->save();
|
||||
$session = $this->assertSession();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals($field_name, '');
|
||||
|
||||
// Create entity with three values.
|
||||
$edit = [
|
||||
$field_name => '1, 2, 3',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
|
||||
// Check that the values were saved.
|
||||
$entity_init = EntityTest::load($id);
|
||||
$this->assertFieldValues($entity_init, $field_name, [1, 2, 3]);
|
||||
|
||||
// Display the form, check that the values are correctly filled in.
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$this->assertSession()->fieldValueEquals($field_name, '1, 2, 3');
|
||||
|
||||
// Submit the form with more values than the field accepts.
|
||||
$edit = [$field_name => '1, 2, 3, 4, 5'];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('this field cannot hold more than 4 values');
|
||||
// Check that the field values were not submitted.
|
||||
$this->assertFieldValues($entity_init, $field_name, [1, 2, 3]);
|
||||
|
||||
// Check that Attributes are rendered on the multivalue container if it is
|
||||
// a multiple widget form.
|
||||
$form->setComponent($field_name, [
|
||||
'type' => 'entity_reference_autocomplete',
|
||||
])
|
||||
->save();
|
||||
$this->drupalGet('entity_test/manage/' . $id . '/edit');
|
||||
$name = str_replace('_', '-', $field_name);
|
||||
$session->responseContains('data-drupal-selector="edit-' . $name . '"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form display of the label for multi-value fields.
|
||||
*/
|
||||
public function testLabelOnMultiValueFields(): void {
|
||||
$user = $this->drupalCreateUser(['administer entity_test content']);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
// Ensure that the 'bar' bundle exists, to avoid config validation errors.
|
||||
EntityTestHelper::createBundle('bar', entity_type: 'entity_test_base_field_display');
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'field_name' => 'foo',
|
||||
'type' => 'text',
|
||||
'cardinality' => FieldStorageConfig::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'field_name' => 'foo',
|
||||
// Set a dangerous label to test XSS filtering.
|
||||
'label' => "<script>alert('a configurable field');</script>",
|
||||
])->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'bar',
|
||||
'mode' => 'default',
|
||||
])->setComponent('foo', ['type' => 'text_textfield'])->enable()->save();
|
||||
|
||||
$entity = EntityTestBaseFieldDisplay::create(['type' => 'bar']);
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet('entity_test_base_field_display/manage/' . $entity->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('A field with multiple values');
|
||||
// Test if labels were XSS filtered.
|
||||
$this->assertSession()->assertEscaped("<script>alert('a configurable field');</script>");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_form_alter().
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetAlter(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_form_alter', 'test_field_widget_multiple');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_form_alter() with single value elements.
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetAlterSingleValues(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_form_alter', 'test_field_widget_multiple_single_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_WIDGET_TYPE_form_alter().
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetTypeAlter(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_WIDGET_TYPE_form_alter', 'test_field_widget_multiple');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_widget_complete_WIDGET_TYPE_form_alter() with single value elements.
|
||||
*/
|
||||
public function testFieldFormMultipleWidgetTypeAlterSingleValues(): void {
|
||||
$this->widgetAlterTest('hook_field_widget_complete_WIDGET_TYPE_form_alter', 'test_field_widget_multiple_single_value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget alter hooks for a given hook name.
|
||||
*/
|
||||
protected function widgetAlterTest($hook, $widget) {
|
||||
// Create a field with fixed cardinality, configure the form to use a
|
||||
// "multiple" widget.
|
||||
$field_storage = $this->fieldStorageMultiple;
|
||||
$field_name = $field_storage['field_name'];
|
||||
$this->field['field_name'] = $field_name;
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($this->field)->save();
|
||||
|
||||
// Set a flag in state so that the hook implementations will run.
|
||||
\Drupal::state()->set("field_test.widget_alter_test", [
|
||||
'hook' => $hook,
|
||||
'field_name' => $field_name,
|
||||
'widget' => $widget,
|
||||
]);
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay($this->field['entity_type'], $this->field['bundle'], 'default')
|
||||
->setComponent($field_name, [
|
||||
'type' => $widget,
|
||||
])
|
||||
->save();
|
||||
|
||||
// We need to rebuild hook information after setting the component through
|
||||
// the API.
|
||||
$this->rebuildAll();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->pageTextMatchesCount(1, '/From ' . $hook . '.* prefix on ' . $field_name . ' parent element\./');
|
||||
if ($widget === 'test_field_widget_multiple_single_value') {
|
||||
$suffix_text = "From $hook(): suffix on $field_name child element.";
|
||||
$this->assertEquals($field_storage['cardinality'], substr_count($this->getTextContent(), $suffix_text), "'$suffix_text' was found {$field_storage['cardinality']} times using widget $widget");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
231
web/core/modules/field/tests/src/Functional/NestedFormTest.php
Normal file
231
web/core/modules/field/tests/src/Functional/NestedFormTest.php
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field elements in nested forms.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class NestedFormTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $fieldStorageSingle;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $fieldStorageUnlimited;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected array $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
$this->fieldStorageSingle = [
|
||||
'field_name' => 'field_single',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$this->fieldStorageUnlimited = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
|
||||
$this->field = [
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests Field API form integration within a subform.
|
||||
*/
|
||||
public function testNestedFieldForm(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Add two fields on the 'entity_test'
|
||||
FieldStorageConfig::create($this->fieldStorageSingle)->save();
|
||||
FieldStorageConfig::create($this->fieldStorageUnlimited)->save();
|
||||
$this->field['field_name'] = 'field_single';
|
||||
$this->field['label'] = 'Single field';
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($this->field['field_name'])
|
||||
->save();
|
||||
$this->field['field_name'] = 'field_unlimited';
|
||||
$this->field['label'] = 'Unlimited field';
|
||||
FieldConfig::create($this->field)->save();
|
||||
$display_repository->getFormDisplay($this->field['entity_type'], $this->field['bundle'])
|
||||
->setComponent($this->field['field_name'])
|
||||
->save();
|
||||
|
||||
// Create two entities.
|
||||
$entity_type = 'entity_test';
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type);
|
||||
|
||||
$entity_1 = $storage->create(['id' => 1]);
|
||||
$entity_1->enforceIsNew();
|
||||
$entity_1->field_single->value = 1;
|
||||
$entity_1->field_unlimited->value = 2;
|
||||
$entity_1->save();
|
||||
|
||||
$entity_2 = $storage->create(['id' => 2]);
|
||||
$entity_2->enforceIsNew();
|
||||
$entity_2->field_single->value = 10;
|
||||
$entity_2->field_unlimited->value = 11;
|
||||
$entity_2->save();
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->assertSession()->fieldValueEquals('field_single[0][value]', 1);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[0][value]', 2);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_single][0][value]', 10);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][0][value]', 11);
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$edit = [
|
||||
'field_single[0][value]' => 1,
|
||||
'field_unlimited[0][value]' => 2,
|
||||
'field_unlimited[1][value]' => 3,
|
||||
'entity_2[field_single][0][value]' => 11,
|
||||
'entity_2[field_unlimited][0][value]' => 12,
|
||||
'entity_2[field_unlimited][1][value]' => 13,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$entity_1 = $storage->load(1);
|
||||
$entity_2 = $storage->load(2);
|
||||
$this->assertFieldValues($entity_1, 'field_single', [1]);
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [2, 3]);
|
||||
$this->assertFieldValues($entity_2, 'field_single', [11]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [12, 13]);
|
||||
|
||||
// Submit invalid values and check that errors are reported on the
|
||||
// correct widgets.
|
||||
$edit = [
|
||||
'field_unlimited[1][value]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Unlimited field does not accept the value -1.");
|
||||
// Entity 1: check that the error was flagged on the correct element.
|
||||
$error_field = $this->assertSession()->fieldExists('edit-field-unlimited-1-value');
|
||||
$this->assertTrue($error_field->hasClass('error'));
|
||||
$edit = [
|
||||
'entity_2[field_unlimited][1][value]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Unlimited field does not accept the value -1.");
|
||||
// Entity 2: check that the error was flagged on the correct element.
|
||||
$error_field = $this->assertSession()->fieldExists('edit-entity-2-field-unlimited-1-value');
|
||||
$this->assertTrue($error_field->hasClass('error'));
|
||||
|
||||
// Test that reordering works on both entities.
|
||||
$edit = [
|
||||
'field_unlimited[0][_weight]' => 0,
|
||||
'field_unlimited[1][_weight]' => -1,
|
||||
'entity_2[field_unlimited][0][_weight]' => 0,
|
||||
'entity_2[field_unlimited][1][_weight]' => -1,
|
||||
];
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [3, 2]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [13, 12]);
|
||||
|
||||
// Test the 'add more' buttons.
|
||||
// 'Add more' button in the first entity:
|
||||
$this->drupalGet('test-entity/nested/1/2');
|
||||
$this->submitForm([], 'field_unlimited_add_more');
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[0][value]', 3);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[1][value]', 2);
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[2][value]', '');
|
||||
$this->assertSession()->fieldValueEquals('field_unlimited[3][value]', '');
|
||||
// 'Add more' button in the first entity (changing field values):
|
||||
$edit = [
|
||||
'entity_2[field_unlimited][0][value]' => 13,
|
||||
'entity_2[field_unlimited][1][value]' => 14,
|
||||
'entity_2[field_unlimited][2][value]' => 15,
|
||||
];
|
||||
$this->submitForm($edit, 'entity_2_field_unlimited_add_more');
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][0][value]', 13);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][1][value]', 14);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][2][value]', 15);
|
||||
$this->assertSession()->fieldValueEquals('entity_2[field_unlimited][3][value]', '');
|
||||
// Save the form and check values are saved correctly.
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertFieldValues($entity_1, 'field_unlimited', [3, 2]);
|
||||
$this->assertFieldValues($entity_2, 'field_unlimited', [13, 14, 15]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity level validation within subforms.
|
||||
*/
|
||||
public function testNestedEntityFormEntityLevelValidation(): void {
|
||||
// Create two entities.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('entity_test_constraints');
|
||||
|
||||
$entity_1 = $storage->create();
|
||||
$entity_1->save();
|
||||
|
||||
$entity_2 = $storage->create();
|
||||
$entity_2->save();
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Display the 'combined form'.
|
||||
$this->drupalGet("test-entity-constraints/nested/{$entity_1->id()}/{$entity_2->id()}");
|
||||
$assert_session->hiddenFieldValueEquals('entity_2[changed]', (string) \Drupal::time()->getRequestTime());
|
||||
|
||||
// Submit the form and check that the entities are updated accordingly.
|
||||
$assert_session->hiddenFieldExists('entity_2[changed]')
|
||||
->setValue(\Drupal::time()->getRequestTime() - 86400);
|
||||
$page->pressButton('Save');
|
||||
|
||||
$elements = $this->cssSelect('.entity-2.error');
|
||||
$this->assertCount(1, $elements, 'The whole nested entity form has been correctly flagged with an error class.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Number;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the creation of numeric fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class NumberFieldTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer entity_test fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests decimal field.
|
||||
*/
|
||||
public function testNumberDecimalField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
'settings' => ['precision' => 8, 'scale' => 4],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_decimal',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="0.00"');
|
||||
|
||||
// Submit a signed decimal value within the allowed precision and scale.
|
||||
$value = '-1234.5678';
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($value);
|
||||
|
||||
// Try to create entries with more than one decimal separator; assert fail.
|
||||
$wrong_entries = [
|
||||
'3.14.159',
|
||||
'0..45469',
|
||||
'..4589',
|
||||
'6.459.52',
|
||||
'6.3..25',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
|
||||
// Try to create entries with minus sign not in the first position.
|
||||
$wrong_entries = [
|
||||
'3-3',
|
||||
'4-',
|
||||
'1.3-',
|
||||
'1.2-4',
|
||||
'-10-10',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests integer field.
|
||||
*/
|
||||
public function testNumberIntegerField(): void {
|
||||
$minimum = rand(-4000, -2000);
|
||||
$maximum = rand(2000, 4000);
|
||||
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
$storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'integer',
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => [
|
||||
'min' => $minimum,
|
||||
'max' => $maximum,
|
||||
'prefix' => 'ThePrefix',
|
||||
],
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '4',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_integer',
|
||||
'settings' => [
|
||||
'prefix_suffix' => FALSE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
// Check the storage schema.
|
||||
$expected = [
|
||||
'columns' => [
|
||||
'value' => [
|
||||
'type' => 'int',
|
||||
'unsigned' => '',
|
||||
'size' => 'normal',
|
||||
],
|
||||
],
|
||||
'unique keys' => [],
|
||||
'indexes' => [],
|
||||
'foreign keys' => [],
|
||||
];
|
||||
$this->assertEquals($expected, $storage->getSchema());
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="4"');
|
||||
|
||||
// Submit a valid integer
|
||||
$value = rand($minimum, $maximum);
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Try to set a value below the minimum value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $minimum - 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be higher than or equal to {$minimum}.");
|
||||
|
||||
// Try to set a decimal value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => 1.5,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} is not a valid number.");
|
||||
|
||||
// Try to set a value above the maximum value
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $maximum + 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be lower than or equal to {$maximum}.");
|
||||
|
||||
// Try to set a wrong integer value.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => '20-40',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
|
||||
// Test with valid entries.
|
||||
$valid_entries = [
|
||||
'-1234',
|
||||
'0',
|
||||
'1234',
|
||||
];
|
||||
|
||||
foreach ($valid_entries as $valid_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $valid_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->assertSession()->responseContains($valid_entry);
|
||||
// Verify that the "content" attribute is not present since the Prefix is
|
||||
// not being displayed.
|
||||
$this->assertSession()->elementNotExists('xpath', '//div[@content="' . $valid_entry . '"]');
|
||||
}
|
||||
|
||||
// Test for the content attribute when a Prefix is displayed. Presumably
|
||||
// this also tests for the attribute when a Suffix is displayed.
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_integer',
|
||||
'settings' => [
|
||||
'prefix_suffix' => TRUE,
|
||||
],
|
||||
])
|
||||
->save();
|
||||
$integer_value = '123';
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $integer_value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
$this->drupalGet('entity_test/' . $id);
|
||||
// Verify that the "content" attribute has been set to the value of the
|
||||
// field, and the prefix is being displayed.
|
||||
$this->assertSession()->elementTextContains('xpath', '//div[@content="' . $integer_value . '"]', 'ThePrefix' . $integer_value);
|
||||
|
||||
$field_configuration_url = 'entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field_name;
|
||||
$this->drupalGet($field_configuration_url);
|
||||
|
||||
// Tests Number validation messages.
|
||||
$edit = [
|
||||
'settings[min]' => 10,
|
||||
'settings[max]' => 8,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("The minimum value must be less than or equal to {$edit['settings[max]']}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests float field.
|
||||
*/
|
||||
public function testNumberFloatField(): void {
|
||||
// Create a field with settings to validate.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'float',
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$display_repository->getViewDisplay('entity_test', 'entity_test')
|
||||
->setComponent($field_name, [
|
||||
'type' => 'number_decimal',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display creation form.
|
||||
$this->drupalGet('entity_test/add');
|
||||
$this->assertSession()->fieldValueEquals("{$field_name}[0][value]", '');
|
||||
$this->assertSession()->responseContains('placeholder="0.00"');
|
||||
|
||||
// Submit a signed decimal value within the allowed precision and scale.
|
||||
$value = -1234.5678;
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $value,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
||||
$id = $match[1];
|
||||
$this->assertSession()->pageTextContains('entity_test ' . $id . ' has been created.');
|
||||
|
||||
// Ensure that the 'number_decimal' formatter displays the number with the
|
||||
// expected rounding.
|
||||
$this->drupalGet('entity_test/' . $id);
|
||||
$this->assertSession()->responseContains(round($value, 2));
|
||||
|
||||
// Try to create entries with more than one decimal separator; assert fail.
|
||||
$wrong_entries = [
|
||||
'3.14.159',
|
||||
'0..45469',
|
||||
'..4589',
|
||||
'6.459.52',
|
||||
'6.3..25',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
|
||||
// Try to create entries with minus sign not in the first position.
|
||||
$wrong_entries = [
|
||||
'3-3',
|
||||
'4-',
|
||||
'1.3-',
|
||||
'1.2-4',
|
||||
'-10-10',
|
||||
];
|
||||
|
||||
foreach ($wrong_entries as $wrong_entry) {
|
||||
$this->drupalGet('entity_test/add');
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $wrong_entry,
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("{$field_name} must be a number.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting minimum values through the interface.
|
||||
*/
|
||||
public function testMinimumValues(): void {
|
||||
// Create a float field.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'float',
|
||||
])->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set the minimum value to a float value.
|
||||
$this->assertSetMinimumValue($field, 0.0001);
|
||||
// Set the minimum value to an integer value.
|
||||
$this->assertSetMinimumValue($field, 1);
|
||||
|
||||
// Create a decimal field.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
])->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Set the minimum value to a decimal value.
|
||||
$this->assertSetMinimumValue($field, 0.1);
|
||||
// Set the minimum value to an integer value.
|
||||
$this->assertSetMinimumValue($field, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to set the minimum value of a field.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertSetMinimumValue(FieldConfigInterface $field, $minimum_value): void {
|
||||
$field_configuration_url = 'entity_test/structure/entity_test/fields/entity_test.entity_test.' . $field->getName();
|
||||
|
||||
// Set the minimum value.
|
||||
$edit = [
|
||||
'settings[min]' => $minimum_value,
|
||||
];
|
||||
$this->drupalGet($field_configuration_url);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
// Check if an error message is shown.
|
||||
$this->assertSession()->pageTextNotContains("Minimum is not a valid number.");
|
||||
// Check if a success message is shown.
|
||||
$this->assertSession()->pageTextContains("Saved {$field->getLabel()} configuration.");
|
||||
// Check if the minimum value was actually set.
|
||||
$this->drupalGet($field_configuration_url);
|
||||
$this->assertSession()->fieldValueEquals('edit-settings-min', $minimum_value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\Traits\Core\CronRunTrait;
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field module after being disabled and re-enabled.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class ReEnableModuleFieldTest extends BrowserTestBase {
|
||||
|
||||
use CronRunTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'node',
|
||||
// We use telephone module instead of test_field because test_field is
|
||||
// hidden and does not display on the admin/modules page.
|
||||
'telephone',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalCreateContentType(['type' => 'article']);
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'create article content',
|
||||
'edit own article content',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field module after being disabled and re-enabled.
|
||||
*
|
||||
* @see field_system_info_alter()
|
||||
*/
|
||||
public function testReEnabledField(): void {
|
||||
// Add a telephone field to the article content type.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_telephone',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'article',
|
||||
'label' => 'Telephone Number',
|
||||
])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
$display_repository->getFormDisplay('node', 'article')
|
||||
->setComponent('field_telephone', [
|
||||
'type' => 'telephone_default',
|
||||
'settings' => [
|
||||
'placeholder' => '123-456-7890',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$display_repository->getViewDisplay('node', 'article')
|
||||
->setComponent('field_telephone', [
|
||||
'type' => 'telephone_link',
|
||||
'weight' => 1,
|
||||
])
|
||||
->save();
|
||||
|
||||
// Display the article node form and verify the telephone widget is present.
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->fieldValueEquals("field_telephone[0][value]", '');
|
||||
|
||||
// Submit an article node with a telephone field so data exist for the
|
||||
// field.
|
||||
$edit = [
|
||||
'title[0][value]' => $this->randomMachineName(),
|
||||
'field_telephone[0][value]' => "123456789",
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->responseContains('<a href="tel:123456789">');
|
||||
|
||||
// Test that the module can't be uninstalled from the UI while there is data
|
||||
// for its fields.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer modules',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains("The Telephone number field type is used in the following field: node.field_telephone");
|
||||
|
||||
// Add another telephone field to a different entity type in order to test
|
||||
// the message for the case when multiple fields are blocking the
|
||||
// uninstallation of a module.
|
||||
$field_storage2 = FieldStorageConfig::create([
|
||||
'field_name' => 'field_telephone_2',
|
||||
'entity_type' => 'user',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage2->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage2,
|
||||
'bundle' => 'user',
|
||||
'label' => 'User Telephone Number',
|
||||
])->save();
|
||||
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains("The Telephone number field type is used in the following fields: node.field_telephone, user.field_telephone_2");
|
||||
|
||||
// Delete both fields.
|
||||
$field_storage->delete();
|
||||
$field_storage2->delete();
|
||||
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains('Uninstall');
|
||||
$this->assertSession()->pageTextContains('Fields pending deletion');
|
||||
$this->cronRun();
|
||||
$this->drupalGet('admin/modules/uninstall');
|
||||
$this->assertSession()->pageTextContains('Uninstall');
|
||||
$this->assertSession()->pageTextNotContains("The Telephone number field type is used in the following field: node.field_telephone");
|
||||
$this->assertSession()->pageTextNotContains('Fields pending deletion');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonAnonTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonBasicAuthTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigJsonCookieTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Resource test base for the FieldConfig entity.
|
||||
*/
|
||||
abstract class FieldConfigResourceTestBase extends ConfigEntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'field_config';
|
||||
|
||||
/**
|
||||
* @var \Drupal\field\FieldConfigInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer node fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$camelids = NodeType::create([
|
||||
'name' => 'Camelids',
|
||||
'type' => 'camelids',
|
||||
]);
|
||||
$camelids->save();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_llama',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$entity = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'camelids',
|
||||
]);
|
||||
$entity->save();
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'bundle' => 'camelids',
|
||||
'default_value' => [],
|
||||
'default_value_callback' => '',
|
||||
'dependencies' => [
|
||||
'config' => [
|
||||
'field.storage.node.field_llama',
|
||||
'node.type.camelids',
|
||||
],
|
||||
'module' => [
|
||||
'text',
|
||||
],
|
||||
],
|
||||
'description' => '',
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'field_llama',
|
||||
'field_type' => 'text',
|
||||
'id' => 'node.camelids.field_llama',
|
||||
'label' => 'field_llama',
|
||||
'langcode' => 'en',
|
||||
'required' => FALSE,
|
||||
'settings' => [
|
||||
'allowed_formats' => [],
|
||||
],
|
||||
'status' => TRUE,
|
||||
'translatable' => TRUE,
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return [
|
||||
'user.permissions',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
return "The 'administer node fields' permission is required.";
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlAnonTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlBasicAuthTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldConfigXmlCookieTest extends FieldConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonAnonTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonBasicAuthTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigJsonCookieTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\ConfigEntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* Resource test base for the FieldStorageConfig entity.
|
||||
*/
|
||||
abstract class FieldStorageConfigResourceTestBase extends ConfigEntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'field_storage_config';
|
||||
|
||||
/**
|
||||
* @var \Drupal\field\FieldConfigStorage
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
$this->grantPermissionsToTestedRole(['administer node fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'true_llama',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
return $field_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'cardinality' => 1,
|
||||
'custom_storage' => FALSE,
|
||||
'dependencies' => [
|
||||
'module' => ['node'],
|
||||
],
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'true_llama',
|
||||
'id' => 'node.true_llama',
|
||||
'indexes' => [],
|
||||
'langcode' => 'en',
|
||||
'locked' => FALSE,
|
||||
'module' => 'core',
|
||||
'persist_with_no_fields' => FALSE,
|
||||
'settings' => [],
|
||||
'status' => TRUE,
|
||||
'translatable' => TRUE,
|
||||
'type' => 'boolean',
|
||||
'uuid' => $this->entity->uuid(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
// @todo Update in https://www.drupal.org/node/2300677.
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
return "The 'administer node fields' permission is required.";
|
||||
|
||||
default:
|
||||
return parent::getExpectedUnauthorizedAccessMessage($method);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlAnonTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlBasicAuthTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class FieldStorageConfigXmlCookieTest extends FieldStorageConfigResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
use XmlEntityNormalizationQuirksTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'xml';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'text/xml; charset=UTF-8';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
|
||||
/**
|
||||
* Tests multilingual fields logic that require a full environment.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class TranslationWebTest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['language', 'field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the field to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The name of the entity type to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test_mulrev';
|
||||
|
||||
/**
|
||||
* The field storage to use in this test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field to use in this test.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldConfig
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldName = $this->randomMachineName() . '_field_name';
|
||||
|
||||
$field_storage = [
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$this->fieldStorage = FieldStorageConfig::load($this->entityTypeId . '.' . $this->fieldName);
|
||||
|
||||
$field = [
|
||||
'field_storage' => $this->fieldStorage,
|
||||
'bundle' => $this->entityTypeId,
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
$this->field = FieldConfig::load($this->entityTypeId . '.' . $field['bundle'] . '.' . $this->fieldName);
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($this->entityTypeId, $this->entityTypeId)
|
||||
->setComponent($this->fieldName)
|
||||
->save();
|
||||
|
||||
for ($i = 0; $i < 3; ++$i) {
|
||||
ConfigurableLanguage::create([
|
||||
'id' => 'l' . $i,
|
||||
'label' => $this->randomString(),
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field translations when creating a new revision.
|
||||
*/
|
||||
public function testFieldFormTranslationRevisions(): void {
|
||||
$web_user = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($web_user);
|
||||
|
||||
// Prepare the field translations.
|
||||
FieldTestHelper::entityInfoTranslatable($this->entityTypeId, TRUE);
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create();
|
||||
$available_langcodes = array_flip(array_keys($this->container->get('language_manager')->getLanguages()));
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
|
||||
// Store the field translations.
|
||||
ksort($available_langcodes);
|
||||
$entity->langcode->value = key($available_langcodes);
|
||||
foreach ($available_langcodes as $langcode => $value) {
|
||||
$translation = $entity->hasTranslation($langcode) ? $entity->getTranslation($langcode) : $entity->addTranslation($langcode);
|
||||
$translation->{$field_name}->value = $value + 1;
|
||||
}
|
||||
$entity->save();
|
||||
|
||||
// Create a new revision.
|
||||
$edit = [
|
||||
"{$field_name}[0][value]" => $entity->{$field_name}->value,
|
||||
'revision' => TRUE,
|
||||
];
|
||||
$this->drupalGet($this->entityTypeId . '/manage/' . $entity->id() . '/edit');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check translation revisions.
|
||||
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId(), $available_langcodes);
|
||||
$this->checkTranslationRevisions($entity->id(), $entity->getRevisionId() + 1, $available_langcodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests translation revisions.
|
||||
*
|
||||
* Check if the field translation attached to the entity revision identified
|
||||
* by the passed arguments were correctly stored.
|
||||
*/
|
||||
private function checkTranslationRevisions($id, $revision_id, $available_langcodes): void {
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$entity = $storage->loadRevision($revision_id);
|
||||
foreach ($available_langcodes as $langcode => $value) {
|
||||
$passed = $entity->getTranslation($langcode)->{$field_name}->value == $value + 1;
|
||||
$this->assertTrue($passed, "The $langcode translation for revision {$entity->getRevisionId()} was correctly stored");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\views\Functional\ViewTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Provides some helper methods for testing fieldapi integration into views.
|
||||
*
|
||||
* @todo Test on a generic entity not on a node. What has to be tested:
|
||||
* - Make sure that every wanted field is added to the according entity type.
|
||||
* - Make sure the joins are done correctly.
|
||||
* - Use basic fields and make sure that the full wanted object is built.
|
||||
* - Use relationships between different entity types, for example node and
|
||||
* the node author(user).
|
||||
*/
|
||||
abstract class FieldTestBase extends ViewTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_test_views'];
|
||||
|
||||
/**
|
||||
* Stores the field definitions used by the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fieldStorages;
|
||||
|
||||
/**
|
||||
* Stores the fields of the field storage.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $fields;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['field_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
|
||||
// Ensure the page node type exists.
|
||||
NodeType::create([
|
||||
'type' => 'page',
|
||||
'name' => 'page',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up field storages for testing.
|
||||
*/
|
||||
public function setUpFieldStorages($amount = 3, $type = 'string') {
|
||||
// Create three fields.
|
||||
$field_names = [];
|
||||
for ($i = 0; $i < $amount; $i++) {
|
||||
$field_names[$i] = 'field_name_' . $i;
|
||||
$this->fieldStorages[$i] = FieldStorageConfig::create([
|
||||
'field_name' => $field_names[$i],
|
||||
'entity_type' => 'node',
|
||||
'type' => $type,
|
||||
]);
|
||||
$this->fieldStorages[$i]->save();
|
||||
}
|
||||
return $field_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up fields for a given bundle.
|
||||
*/
|
||||
public function setUpFields($bundle = 'page') {
|
||||
foreach ($this->fieldStorages as $key => $field_storage) {
|
||||
$this->fields[$key] = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
]);
|
||||
$this->fields[$key]->save();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Functional\Views;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests the UI of the field handler.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\field\Plugin\views\field\Field
|
||||
*/
|
||||
class FieldUITest extends FieldTestBase {
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = ['test_view_fieldapi'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['views_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with the 'administer views' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE, $modules = ['field_test_views']): void {
|
||||
parent::setUp($import_test_views, $modules);
|
||||
|
||||
$this->account = $this->drupalCreateUser(['administer views']);
|
||||
$this->drupalLogin($this->account);
|
||||
|
||||
$this->setUpFieldStorages(1, 'text');
|
||||
$this->setUpFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic field handler settings in the UI.
|
||||
*/
|
||||
public function testHandlerUI(): void {
|
||||
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
|
||||
$this->drupalGet($url);
|
||||
|
||||
// Tests the available formatter options.
|
||||
$options = $this->assertSession()->selectExists('edit-options-type')->findAll('css', 'option');
|
||||
$options = array_map(function ($item) {
|
||||
return $item->getValue();
|
||||
}, $options);
|
||||
$this->assertEqualsCanonicalizing(['text_default', 'text_trimmed'], $options);
|
||||
|
||||
$this->submitForm(['options[type]' => 'text_trimmed'], 'Apply');
|
||||
|
||||
$this->drupalGet($url);
|
||||
$this->assertTrue($this->assertSession()->optionExists('edit-options-type', 'text_trimmed')->isSelected());
|
||||
|
||||
$random_number = rand(100, 400);
|
||||
$this->submitForm(['options[settings][trim_length]' => $random_number], 'Apply');
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->fieldValueEquals('options[settings][trim_length]', $random_number);
|
||||
|
||||
// Save the view and test whether the settings are saved.
|
||||
$this->drupalGet('admin/structure/views/view/test_view_fieldapi');
|
||||
$this->submitForm([], 'Save');
|
||||
$view = Views::getView('test_view_fieldapi');
|
||||
$view->initHandlers();
|
||||
$this->assertEquals('text_trimmed', $view->field['field_name_0']->options['type']);
|
||||
$this->assertEquals($random_number, $view->field['field_name_0']->options['settings']['trim_length']);
|
||||
|
||||
// Now change the formatter back to 'default' which doesn't have any
|
||||
// settings. We want to ensure that the settings are empty then.
|
||||
$edit['options[type]'] = 'text_default';
|
||||
$this->drupalGet('admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
$this->drupalGet('admin/structure/views/view/test_view_fieldapi');
|
||||
$this->submitForm([], 'Save');
|
||||
$view = Views::getView('test_view_fieldapi');
|
||||
$view->initHandlers();
|
||||
$this->assertEquals('text_default', $view->field['field_name_0']->options['type']);
|
||||
$this->assertEquals([], $view->field['field_name_0']->options['settings']);
|
||||
|
||||
// Ensure that the view depends on the field storage.
|
||||
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependencies('config', [$this->fieldStorages[0]->getConfigDependencyName()]);
|
||||
$this->assertTrue(isset($dependencies['views.view.test_view_fieldapi']), 'The view is dependent on the field storage.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the basic field handler form when aggregation is enabled.
|
||||
*/
|
||||
public function testHandlerUIAggregation(): void {
|
||||
// Enable aggregation.
|
||||
$edit = ['group_by' => '1'];
|
||||
$this->drupalGet('admin/structure/views/nojs/display/test_view_fieldapi/default/group_by');
|
||||
$this->submitForm($edit, 'Apply');
|
||||
|
||||
$url = "admin/structure/views/nojs/handler/test_view_fieldapi/default/field/field_name_0";
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
|
||||
// Test the click sort column options.
|
||||
// Tests the available formatter options.
|
||||
$options = $this->assertSession()->selectExists('edit-options-click-sort-column')->findAll('css', 'option');
|
||||
$options = array_map(function ($item) {
|
||||
return $item->getValue();
|
||||
}, $options);
|
||||
$this->assertEqualsCanonicalizing(['format', 'value'], $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a boolean field filter handler.
|
||||
*/
|
||||
public function testBooleanFilterHandler(): void {
|
||||
// Create a boolean field.
|
||||
$field_name = 'field_boolean';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'page',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$url = "admin/structure/views/nojs/add-handler/test_view_fieldapi/default/filter";
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm([
|
||||
'name[node__' . $field_name . '.' . $field_name . '_value]' => TRUE,
|
||||
], 'Add and configure filter criteria');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
// Verify that using a boolean field as a filter also results in using the
|
||||
// boolean plugin.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-1"]', 'True');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-0"]', 'False');
|
||||
|
||||
// Expose the filter and see if the 'Any' option is added and if we can save
|
||||
// it.
|
||||
$this->submitForm([], 'Expose filter');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//label[@for="edit-options-value-all"]', '- Any -');
|
||||
$this->submitForm(['options[value]' => 'All', 'options[expose][required]' => FALSE], 'Apply');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->drupalGet('/admin/structure/views/nojs/handler/test_view_fieldapi/default/filter/field_boolean_value');
|
||||
$this->assertSession()->checkboxChecked('edit-options-value-all');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\Boolean;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Boolean field formatter settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFormatterSettingsTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'field_ui', 'text', 'node', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the entity bundle that is created in the test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* The name of the Boolean field to use for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a content type. Use Node because it has Field UI pages that work.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->bundle = $type->id();
|
||||
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer nodes',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->fieldName = $this->randomMachineName(8);
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$display = \Drupal::service('entity_display.repository')->getViewDisplay('node', $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'boolean',
|
||||
'settings' => [],
|
||||
]);
|
||||
$display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the formatter settings page for the Boolean formatter.
|
||||
*/
|
||||
public function testBooleanFormatterSettings(): void {
|
||||
// List the options we expect to see on the settings form. Omit the one
|
||||
// with the Unicode check/x characters, which does not appear to work
|
||||
// well in BrowserTestBase.
|
||||
$options = [
|
||||
'Yes / No',
|
||||
'True / False',
|
||||
'On / Off',
|
||||
'Enabled / Disabled',
|
||||
'1 / 0',
|
||||
'Custom',
|
||||
];
|
||||
|
||||
// For several different values of the field settings, test that the
|
||||
// options, including default, are shown correctly.
|
||||
$settings = [
|
||||
['Yes', 'No'],
|
||||
['On', 'Off'],
|
||||
['TRUE', 'FALSE'],
|
||||
];
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
foreach ($settings as $values) {
|
||||
// Set up the field settings.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/fields/node.' . $this->bundle . '.' . $this->fieldName);
|
||||
$this->submitForm([
|
||||
'settings[on_label]' => $values[0],
|
||||
'settings[off_label]' => $values[1],
|
||||
], 'Save settings');
|
||||
|
||||
$assert_session->waitForText('Saved ' . $this->fieldName . ' configuration.');
|
||||
// Open the Manage Display page and trigger the field settings form.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->bundle . '/display');
|
||||
$this->getSession()->getPage()->pressButton($this->fieldName . '_settings_edit');
|
||||
$assert_session->waitForElement('css', '.ajax-new-content');
|
||||
|
||||
// Test that the settings options are present in the correct format.
|
||||
foreach ($options as $string) {
|
||||
$assert_session->pageTextContains($string);
|
||||
}
|
||||
$assert_session->pageTextContains("Field settings ({$values[0]} / {$values[1]})");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\EntityReference;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests for the administrative UI.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceAdminTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable path module to ensure that the selection handler does not fail for
|
||||
* entities with a path field.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'block',
|
||||
'field_ui',
|
||||
'path',
|
||||
'taxonomy',
|
||||
'block',
|
||||
'views_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The name of the content type created for testing purposes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* Name of a second content type to be used as a target of entity references.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $targetType;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
// Create a second content type, to be a target for entity reference fields.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->targetType = $type->id();
|
||||
|
||||
// Change the title field label.
|
||||
$fields = \Drupal::service('entity_field.manager')
|
||||
->getFieldDefinitions('node', $type->id());
|
||||
$fields['title']->getConfig($type->id())
|
||||
->setLabel($type->id() . ' title')->save();
|
||||
|
||||
// Add text field to the second content type.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
'entity_types' => ['node'],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'label' => 'Text Field',
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->targetType,
|
||||
'settings' => [],
|
||||
'required' => FALSE,
|
||||
])->save();
|
||||
|
||||
// Create test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer views',
|
||||
'create ' . $this->type . ' content',
|
||||
'edit own ' . $this->type . ' content',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Entity Reference Admin UI.
|
||||
*/
|
||||
public function testFieldAdminHandler(): void {
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
/** @var \Drupal\FunctionalJavascriptTests\JSWebAssert $assert_session */
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// First step: 'Add new field' on the 'Manage fields' page.
|
||||
$this->drupalGet($bundle_path . '/fields');
|
||||
$this->clickLink('Create a new field');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
// Check if the commonly referenced entity types appear in the list.
|
||||
$this->clickLink('Reference');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($assert_session->waitForText('Choose a field type'));
|
||||
$this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:node']");
|
||||
$this->assertSession()->elementExists('css', "[name='field_options_wrapper'][value='field_ui:entity_reference:user']");
|
||||
|
||||
$this->assertSession()->buttonExists('Change field type')->press();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->fieldUIAddNewFieldJS(NULL, 'test', 'Test', 'entity_reference', FALSE);
|
||||
|
||||
// Node should be selected by default.
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][settings][target_type]', 'node');
|
||||
|
||||
// Check that all entity types can be referenced.
|
||||
$this->assertFieldSelectOptions('field_storage[subform][settings][target_type]', array_keys(\Drupal::entityTypeManager()->getDefinitions()));
|
||||
|
||||
// The base handler should be selected by default.
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'default:node');
|
||||
|
||||
// The base handler settings should be displayed.
|
||||
$entity_type_id = 'node';
|
||||
// Check that the type label is correctly displayed.
|
||||
$assert_session->pageTextContains('Content type');
|
||||
// Check that sort options are not yet visible.
|
||||
$sort_by = $page->findField('settings[handler_settings][sort][field]');
|
||||
$this->assertNotEmpty($sort_by);
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden.');
|
||||
$bundles = $this->container->get('entity_type.bundle.info')->getBundleInfo($entity_type_id);
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
}
|
||||
|
||||
reset($bundles);
|
||||
|
||||
// Initially, no bundles are selected so no sort options are available.
|
||||
$this->assertFieldSelectOptions('settings[handler_settings][sort][field]', ['_none']);
|
||||
|
||||
// Select this bundle so that standard sort options are available.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->type . ']')->setValue($this->type);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// Test that a non-translatable base field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
// Test that a translatable base field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
// Test that a configurable field is a sort option.
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
// Test that a field not on this bundle is not a sort option.
|
||||
$assert_session->optionNotExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
// Test that the title option appears once, with the default label.
|
||||
$title_options = $sort_by->findAll('xpath', 'option[@value="title"]');
|
||||
$this->assertEquals(1, count($title_options));
|
||||
$this->assertEquals('Title', $title_options[0]->getText());
|
||||
|
||||
// Also select the target bundle so that field_text is also available.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
|
||||
// Select only the target bundle. The options should be the same.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->type . ']')->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'title');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'body.value');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
// Exception: the title option has a different label.
|
||||
$title_options = $sort_by->findAll('xpath', 'option[@value="title"]');
|
||||
$this->assertEquals(1, count($title_options));
|
||||
$this->assertEquals($this->targetType . ' title', $title_options[0]->getText());
|
||||
|
||||
// Test the sort settings.
|
||||
// Option 0: no sort.
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
|
||||
$sort_direction = $page->findField('settings[handler_settings][sort][direction]');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
// Option 1: sort by field.
|
||||
$sort_by->setValue('nid');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_direction->isVisible());
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][direction]', 'ASC');
|
||||
|
||||
// Test that the sort-by options are sorted.
|
||||
$labels = array_map(function (NodeElement $element) {
|
||||
return $element->getText();
|
||||
}, $sort_by->findAll('xpath', 'option'));
|
||||
for ($i = count($labels) - 1, $sorted = TRUE; $i > 0; --$i) {
|
||||
if ($labels[$i - 1] > $labels[$i]) {
|
||||
$sorted = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($sorted, 'The "sort by" options are sorted.');
|
||||
|
||||
// Set back to no sort.
|
||||
$sort_by->setValue('_none');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
|
||||
// Sort by nid, then select no bundles. The sort fields and sort direction
|
||||
// should not display.
|
||||
$sort_by->setValue('nid');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
foreach ($bundles as $bundle_name => $bundle_info) {
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
$checkbox = $page->findField('settings[handler_settings][target_bundles][' . $bundle_name . ']');
|
||||
if ($checkbox->isChecked()) {
|
||||
$checkbox->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
}
|
||||
}
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden.');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
|
||||
// Select a bundle and check the same two fields.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_by->isVisible(), 'The "sort by" options are visible.');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'field_text.value');
|
||||
|
||||
// Un-select the bundle and check the same two fields.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->uncheck();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertFalse($sort_by->isVisible(), 'The "sort by" options are hidden yet again.');
|
||||
$this->assertFieldSelectOptions('settings[handler_settings][sort][field]', ['_none']);
|
||||
|
||||
// Third step: confirm.
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $this->targetType . ']')->setValue($this->targetType);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
// @todo remove after https://www.drupal.org/i/3395590 has been fixed.
|
||||
$this->getSession()->resizeWindow(1200, 1500);
|
||||
usleep(5000);
|
||||
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Save');
|
||||
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$this->assertSession()->elementTextContains('xpath', '//table[@id="field-overview"]//tr[@id="field-test"]/td[1]', "Test");
|
||||
|
||||
// Check that the field settings form can be submitted again, even when the
|
||||
// field is required.
|
||||
// The first 'Edit' link is for the Body field.
|
||||
$this->drupalGet($bundle_path . '/fields');
|
||||
$this->clickLink('Edit', 1);
|
||||
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$this->assertNotNull($button_set = $assert_session->waitForElement('css', '.ui-dialog-buttonset'));
|
||||
$button_set->pressButton('Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Switch the target type to 'taxonomy_term' and check that the settings
|
||||
// specific to its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$this->assertTrue($assert_session->waitForText('These settings apply to the Test field everywhere it is used.'));
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('taxonomy_term');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->fieldExists('settings[handler_settings][auto_create]');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler]', 'default:taxonomy_term');
|
||||
|
||||
// Switch the target type to 'user' and check that the settings specific to
|
||||
// its selection handler are displayed.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$target_type_input = $assert_session->fieldExists('field_storage[subform][settings][target_type]');
|
||||
$target_type_input->setValue('user');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][filter][type]', '_none');
|
||||
$this->assertSession()->fieldValueEquals('settings[handler_settings][sort][field]', '_none');
|
||||
$assert_session->optionNotExists('settings[handler_settings][sort][field]', 'nid');
|
||||
$assert_session->optionExists('settings[handler_settings][sort][field]', 'uid');
|
||||
|
||||
// Check that sort direction is visible only when a sort field is selected.
|
||||
$sort_direction = $page->findField('settings[handler_settings][sort][direction]');
|
||||
$this->assertFalse($sort_direction->isVisible());
|
||||
$sort_by->setValue('name');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$this->assertTrue($sort_direction->isVisible());
|
||||
|
||||
// Switch the target type to 'node'.
|
||||
$field_name = 'node.' . $this->type . '.field_test';
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('node');
|
||||
|
||||
// Try to select the views handler.
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$create = Url::fromRoute('views_ui.add')->toString();
|
||||
$existing = Url::fromRoute('entity.view.collection')->toString();
|
||||
$views_text = 'No eligible views were found. <a href="' . $create . '">Create a view</a> with an <em>Entity Reference</em> display, or add such a display to an <a href="' . $existing . '">existing view</a>.';
|
||||
$assert_session->waitForElement('xpath', '//a[contains(text(), "Create a view")]');
|
||||
$assert_session->responseContains($views_text);
|
||||
|
||||
$this->submitForm([], 'Save settings');
|
||||
// If no eligible view is available we should see a message.
|
||||
$this->assertTrue($assert_session->waitForText('The views entity selection mode requires a view.'));
|
||||
|
||||
// Enable the entity_reference_test module which creates an eligible view.
|
||||
$this->container->get('module_installer')
|
||||
->install(['entity_reference_test']);
|
||||
$this->resetAll();
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session
|
||||
->waitForField('settings[handler_settings][view][view_and_display]')
|
||||
->setValue('test_entity_reference:entity_reference_1');
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
|
||||
// Switch the target type to 'entity_test'.
|
||||
$this->drupalGet($bundle_path . '/fields/' . $field_name);
|
||||
$page->findField('field_storage[subform][settings][target_type]')->setValue('entity_test');
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$page->findField('settings[handler]')->setValue('views');
|
||||
$assert_session->waitForField('settings[handler_settings][view][view_and_display]');
|
||||
$page
|
||||
->findField('settings[handler_settings][view][view_and_display]')
|
||||
->selectOption('test_entity_reference_entity_test:entity_reference_1');
|
||||
$this->assertSession()->fieldExists('required')->check();
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertTrue($assert_session->waitForText('Saved Test configuration.'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param string $name
|
||||
* The field name.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSelectOptions(string $name, array $expected_options): void {
|
||||
$field = $this->assertSession()->selectExists($name);
|
||||
$options = $field->findAll('xpath', 'option');
|
||||
$optgroups = $field->findAll('xpath', 'optgroup');
|
||||
$nested_options = [];
|
||||
foreach ($optgroups as $optgroup) {
|
||||
$nested_options[] = $optgroup->findAll('xpath', 'option');
|
||||
}
|
||||
$options = array_merge($options, ...$nested_options);
|
||||
array_walk($options, function (NodeElement &$option) {
|
||||
$option = $option->getAttribute('value');
|
||||
});
|
||||
$this->assertEqualsCanonicalizing($expected_options, $options);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests widget form for a multiple value field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MultipleValueWidgetTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_test', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$account = $this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
$this->drupalLogin($account);
|
||||
|
||||
$field = [
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => '[site:name]_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_unlimited',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
])->save();
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
$entity_form_display = EntityFormDisplay::load($field['entity_type'] . '.' . $field['bundle'] . '.default');
|
||||
$entity_form_display->setComponent($field['field_name'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'Add more' functionality.
|
||||
*/
|
||||
public function testFieldMultipleValueWidget(): void {
|
||||
$this->drupalGet('entity_test/add');
|
||||
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
$add_more_button = $page->findButton('field_unlimited_add_more');
|
||||
|
||||
// First set a value on the first input field.
|
||||
$field_0 = $page->findField('field_unlimited[0][value]');
|
||||
$field_0->setValue('1');
|
||||
|
||||
$field_0_remove_button = $page->findButton('field_unlimited_0_remove_button');
|
||||
$this->assertNotEmpty($field_0_remove_button, 'First field has a remove button.');
|
||||
|
||||
// Add another item.
|
||||
$add_more_button->click();
|
||||
$field_1 = $assert_session->waitForField('field_unlimited[1][value]');
|
||||
$this->assertNotEmpty($field_1, 'Successfully added another item.');
|
||||
|
||||
$field_1_remove_button = $page->findButton('field_unlimited_1_remove_button');
|
||||
$this->assertNotEmpty($field_1_remove_button, 'Also second field has a remove button.');
|
||||
|
||||
// Validate the value of the first field has not changed.
|
||||
$this->assertEquals('1', $field_0->getValue(), 'Value for the first item has not changed.');
|
||||
|
||||
// Validate the value of the second item is empty.
|
||||
$this->assertEmpty($field_1->getValue(), 'Value for the second item is currently empty.');
|
||||
|
||||
// Add another item.
|
||||
$add_more_button->click();
|
||||
$field_2 = $assert_session->waitForField('field_unlimited[2][value]');
|
||||
$this->assertNotEmpty($field_2, 'Successfully added another item.');
|
||||
|
||||
// Set values for the 2nd and 3rd fields to validate dragging.
|
||||
$field_1->setValue('2');
|
||||
$field_2->setValue('3');
|
||||
|
||||
$field_weight_0 = $page->findField('field_unlimited[0][_weight]');
|
||||
$field_weight_1 = $page->findField('field_unlimited[1][_weight]');
|
||||
$field_weight_2 = $page->findField('field_unlimited[2][_weight]');
|
||||
|
||||
// Assert starting situation matches expectations.
|
||||
$this->assertGreaterThan($field_weight_0->getValue(), $field_weight_1->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Drag the first row after the third row.
|
||||
$dragged = $field_0->find('xpath', 'ancestor::tr[contains(@class, "draggable")]//a[starts-with(@class, "tabledrag-handle")]');
|
||||
$target = $field_2->find('xpath', 'ancestor::tr[contains(@class, "draggable")]');
|
||||
$dragged->dragTo($target);
|
||||
|
||||
// Assert the order of items is updated correctly after dragging.
|
||||
$this->assertGreaterThan($field_weight_2->getValue(), $field_weight_0->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Validate the order of items conforms to the last drag action after a
|
||||
// updating the form via the server.
|
||||
$add_more_button->click();
|
||||
$field_3 = $assert_session->waitForField('field_unlimited[3][value]');
|
||||
$this->assertNotEmpty($field_3);
|
||||
$this->assertGreaterThan($field_weight_2->getValue(), $field_weight_0->getValue());
|
||||
$this->assertGreaterThan($field_weight_1->getValue(), $field_weight_2->getValue());
|
||||
|
||||
// Validate no extraneous widget is displayed.
|
||||
$element = $page->findField('field_unlimited[4][value]');
|
||||
$this->assertEmpty($element);
|
||||
|
||||
// Test removing items/values.
|
||||
$field_0_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
// Test the updated widget.
|
||||
// First item is the initial second item.
|
||||
$this->assertEquals('2', $field_0->getValue(), 'Value for the first item has changed.');
|
||||
// We do not have the initial first item anymore.
|
||||
$this->assertEmpty($field_2->getValue(), 'Value for the third item is currently empty.');
|
||||
$element = $page->findField('field_unlimited[3][value]');
|
||||
$this->assertEmpty($element);
|
||||
|
||||
// We can also remove empty items.
|
||||
$field_2_remove_button = $page->findButton('field_unlimited_2_remove_button');
|
||||
$field_2_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$element = $page->findField('field_unlimited[2][value]');
|
||||
$this->assertEmpty($element, 'Empty field also removed.');
|
||||
|
||||
// Assert that the wrapper exists and isn't nested.
|
||||
$this->assertSession()->elementsCount('css', '[data-drupal-selector="edit-field-unlimited-wrapper"]', 1);
|
||||
|
||||
// Test removing items/values on saved entities resets to initial value.
|
||||
$this->submitForm([], 'Save');
|
||||
$field_2_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$field_1_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$field_0_remove_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$add_more_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSame('', $field_0->getValue());
|
||||
$add_more_button->click();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSame('', $field_1->getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that no validation occurs on field on "Add more" click.
|
||||
*/
|
||||
public function testFieldMultipleValueWidgetAddMoreNoValidation(): void {
|
||||
// Set unlimited field to be required.
|
||||
$field_name = 'field_unlimited';
|
||||
$field = FieldConfig::loadByName('entity_test', 'entity_test', $field_name);
|
||||
$field->setRequired(TRUE);
|
||||
$field->save();
|
||||
|
||||
$this->drupalGet('entity_test/add');
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Add another item with the first item being empty, even though the field
|
||||
// is required.
|
||||
$add_more_button = $page->findButton('field_unlimited_add_more');
|
||||
$add_more_button->click();
|
||||
$field_1 = $assert_session->waitForField('field_unlimited[1][value]');
|
||||
$this->assertNotEmpty($field_1, 'Successfully added another item.');
|
||||
// Confirm the new item has focus.
|
||||
$this->assertHasFocusByAttribute('name', 'field_unlimited[1][value]');
|
||||
// The first item should not be in error state.
|
||||
$assert_session->elementNotExists('css', 'input[name="field_unlimited[0][value]"].error');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts an element specified by an attribute value has focus.
|
||||
*
|
||||
* @param string $name
|
||||
* The attribute name.
|
||||
* @param string $value
|
||||
* The attribute value.
|
||||
*
|
||||
* @todo Replace with assertHasFocus() in https://drupal.org/i/3041768.
|
||||
*/
|
||||
private function assertHasFocusByAttribute(string $name, string $value): void {
|
||||
$active_element = $this->getSession()->evaluateScript('document.activeElement');
|
||||
$this->assertSame($value, $active_element->attribute($name));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,190 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\FunctionalJavascript\Number;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
* Tests the numeric field widget.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class NumberFieldTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'bypass node access',
|
||||
'administer entity_test fields',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default formatter behavior.
|
||||
*/
|
||||
public function testNumberFormatter(): void {
|
||||
$type = $this->randomMachineName();
|
||||
$float_field = $this->randomMachineName();
|
||||
$integer_field = $this->randomMachineName();
|
||||
$thousand_separators = ['', '.', ',', ' ', chr(8201), "'"];
|
||||
$decimal_separators = ['.', ','];
|
||||
$prefix = $this->randomMachineName();
|
||||
$suffix = $this->randomMachineName();
|
||||
$random_float = rand(0, pow(10, 6));
|
||||
$random_integer = rand(0, pow(10, 6));
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Create a content type containing float and integer fields.
|
||||
$this->drupalCreateContentType(['type' => $type]);
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $float_field,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'float',
|
||||
])->save();
|
||||
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $integer_field,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'integer',
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $float_field,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $type,
|
||||
'settings' => [
|
||||
'prefix' => $prefix,
|
||||
'suffix' => $suffix,
|
||||
],
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => $integer_field,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $type,
|
||||
'settings' => [
|
||||
'prefix' => $prefix,
|
||||
'suffix' => $suffix,
|
||||
],
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')->getFormDisplay('node', $type, 'default')
|
||||
->setComponent($float_field, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->setComponent($integer_field, [
|
||||
'type' => 'number',
|
||||
'settings' => [
|
||||
'placeholder' => '0.00',
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')->getViewDisplay('node', $type)
|
||||
->setComponent($float_field, [
|
||||
'type' => 'number_decimal',
|
||||
])
|
||||
->setComponent($integer_field, [
|
||||
'type' => 'number_unformatted',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Create a node to test formatters.
|
||||
$node = Node::create([
|
||||
'type' => $type,
|
||||
'title' => $this->randomMachineName(),
|
||||
$float_field => ['value' => $random_float],
|
||||
$integer_field => ['value' => $random_integer],
|
||||
]);
|
||||
$node->save();
|
||||
|
||||
// Go to manage display page.
|
||||
$this->drupalGet("admin/structure/types/manage/$type/display");
|
||||
|
||||
// Configure number_decimal formatter for the 'float' field type.
|
||||
$thousand_separator = $thousand_separators[array_rand($thousand_separators)];
|
||||
$decimal_separator = $decimal_separators[array_rand($decimal_separators)];
|
||||
$scale = rand(0, 10);
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->pressButton("{$float_field}_settings_edit");
|
||||
$assert_session->waitForElement('css', '.ajax-new-content');
|
||||
$edit = [
|
||||
"fields[{$float_field}][settings_edit_form][settings][prefix_suffix]" => TRUE,
|
||||
"fields[{$float_field}][settings_edit_form][settings][scale]" => $scale,
|
||||
"fields[{$float_field}][settings_edit_form][settings][decimal_separator]" => $decimal_separator,
|
||||
"fields[{$float_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
|
||||
];
|
||||
foreach ($edit as $name => $value) {
|
||||
$page->fillField($name, $value);
|
||||
}
|
||||
$page->pressButton("{$float_field}_plugin_settings_update");
|
||||
$assert_session->waitForElement('css', '.field-plugin-summary-cell > .ajax-new-content');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Check number_decimal and number_unformatted formatters behavior.
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
$float_formatted = number_format($random_float, $scale, $decimal_separator, $thousand_separator);
|
||||
$this->assertSession()->responseContains("$prefix$float_formatted$suffix");
|
||||
$this->assertSession()->responseContains((string) $random_integer);
|
||||
|
||||
// Configure the number_decimal formatter.
|
||||
\Drupal::service('entity_display.repository')->getViewDisplay('node', $type)
|
||||
->setComponent($integer_field, [
|
||||
'type' => 'number_integer',
|
||||
])
|
||||
->save();
|
||||
$this->drupalGet("admin/structure/types/manage/$type/display");
|
||||
|
||||
$thousand_separator = $thousand_separators[array_rand($thousand_separators)];
|
||||
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->pressButton("{$integer_field}_settings_edit");
|
||||
$assert_session->waitForElement('css', '.ajax-new-content');
|
||||
$edit = [
|
||||
"fields[{$integer_field}][settings_edit_form][settings][prefix_suffix]" => FALSE,
|
||||
"fields[{$integer_field}][settings_edit_form][settings][thousand_separator]" => $thousand_separator,
|
||||
];
|
||||
foreach ($edit as $name => $value) {
|
||||
$page->fillField($name, $value);
|
||||
}
|
||||
$page->pressButton("{$integer_field}_plugin_settings_update");
|
||||
$assert_session->waitForElement('css', '.field-plugin-summary-cell > .ajax-new-content');
|
||||
$this->submitForm([], 'Save');
|
||||
|
||||
// Check number_integer formatter behavior.
|
||||
$this->drupalGet('node/' . $node->id());
|
||||
|
||||
$integer_formatted = number_format($random_integer, 0, '', $thousand_separator);
|
||||
$this->assertSession()->responseContains($integer_formatted);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
|
||||
use Drupal\Core\Config\Action\ConfigActionException;
|
||||
use Drupal\Core\Entity\EntityFieldManagerInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Recipe\RecipeRunner;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* @covers \Drupal\field\Plugin\ConfigAction\AddToAllBundles
|
||||
*
|
||||
* @group Recipe
|
||||
* @group field
|
||||
*/
|
||||
class AddToAllBundlesConfigActionTest extends KernelTestBase {
|
||||
|
||||
use RecipeTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'node', 'system', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
NodeType::create([
|
||||
'type' => 'one',
|
||||
'name' => 'One',
|
||||
])->save();
|
||||
NodeType::create([
|
||||
'type' => 'two',
|
||||
'name' => 'Two',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests instantiating a field on all bundles of an entity type.
|
||||
*/
|
||||
public function testInstantiateNewFieldOnAllBundles(): void {
|
||||
// Ensure the body field doesn't actually exist yet.
|
||||
$storage_definitions = $this->container->get(EntityFieldManagerInterface::class)
|
||||
->getFieldStorageDefinitions('node');
|
||||
$this->assertArrayNotHasKey('body', $storage_definitions);
|
||||
|
||||
$this->applyAction('field.storage.node.body');
|
||||
|
||||
// Fields and expected data exist.
|
||||
/** @var \Drupal\field\FieldConfigInterface[] $body_fields */
|
||||
$body_fields = $this->container->get(EntityTypeManagerInterface::class)
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'entity_type' => 'node',
|
||||
'field_name' => 'body',
|
||||
]);
|
||||
ksort($body_fields);
|
||||
$this->assertSame(['node.one.body', 'node.two.body'], array_keys($body_fields));
|
||||
foreach ($body_fields as $field) {
|
||||
$this->assertSame('Body field label', $field->label());
|
||||
$this->assertSame('Set by config actions.', $field->getDescription());
|
||||
}
|
||||
|
||||
// Expect an error when the 'addToAllBundles' action is invoked on anything
|
||||
// other than a field storage config entity.
|
||||
$this->expectException(PluginNotFoundException::class);
|
||||
$this->expectExceptionMessage('The "user_role" entity does not support the "addToAllBundles" config action.');
|
||||
$this->applyAction('user.role.anonymous');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the action can be set to fail if the field already exists.
|
||||
*/
|
||||
public function testFailIfExists(): void {
|
||||
$this->installConfig('node');
|
||||
node_add_body_field(NodeType::load('one'));
|
||||
|
||||
$this->expectException(ConfigActionException::class);
|
||||
$this->expectExceptionMessage('Field node.one.body already exists.');
|
||||
$this->applyAction('field.storage.node.body', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the action will ignore existing fields by default.
|
||||
*/
|
||||
public function testIgnoreExistingFields(): void {
|
||||
$this->installConfig('node');
|
||||
|
||||
node_add_body_field(NodeType::load('one'))
|
||||
->setLabel('Original label')
|
||||
->setDescription('Original description')
|
||||
->save();
|
||||
|
||||
$this->applyAction('field.storage.node.body');
|
||||
|
||||
// The existing field should not be changed.
|
||||
$field = FieldConfig::loadByName('node', 'one', 'body');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('Original label', $field->label());
|
||||
$this->assertSame('Original description', $field->getDescription());
|
||||
|
||||
// But the new field should be created as expected.
|
||||
$field = FieldConfig::loadByName('node', 'two', 'body');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('Body field label', $field->label());
|
||||
$this->assertSame('Set by config actions.', $field->getDescription());
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a recipe with the addToAllBundles action.
|
||||
*
|
||||
* @param string $config_name
|
||||
* The name of the config object which should run the addToAllBundles
|
||||
* action.
|
||||
* @param bool $fail_if_exists
|
||||
* (optional) Whether the action should fail if the field already exists on
|
||||
* any bundle. Defaults to FALSE.
|
||||
*/
|
||||
private function applyAction(string $config_name, bool $fail_if_exists = FALSE): void {
|
||||
$fail_if_exists = var_export($fail_if_exists, TRUE);
|
||||
$contents = <<<YAML
|
||||
name: Instantiate field on all bundles
|
||||
config:
|
||||
import:
|
||||
node:
|
||||
- field.storage.node.body
|
||||
actions:
|
||||
$config_name:
|
||||
addToAllBundles:
|
||||
label: Body field label
|
||||
description: Set by config actions.
|
||||
fail_if_exists: $fail_if_exists
|
||||
YAML;
|
||||
$recipe = $this->createRecipe($contents);
|
||||
RecipeRunner::processRecipe($recipe);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Boolean;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the boolean formatter.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanFormatterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'text',
|
||||
'entity_test',
|
||||
'user',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['field']);
|
||||
$this->installEntitySchema('entity_test');
|
||||
|
||||
$this->entityType = 'entity_test';
|
||||
$this->bundle = $this->entityType;
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityType,
|
||||
'type' => 'boolean',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$this->display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'boolean',
|
||||
'settings' => [],
|
||||
]);
|
||||
$this->display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders fields of a given entity with a given display.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity object with attached fields to render.
|
||||
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
||||
* The display to render the fields in.
|
||||
*
|
||||
* @return string
|
||||
* The rendered entity fields.
|
||||
*/
|
||||
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||
$content = $display->build($entity);
|
||||
$content = $this->render($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests boolean formatter output.
|
||||
*/
|
||||
public function testBooleanFormatter(): void {
|
||||
$data = [];
|
||||
$data[] = [0, [], 'Off'];
|
||||
$data[] = [1, [], 'On'];
|
||||
|
||||
$format = ['format' => 'enabled-disabled'];
|
||||
$data[] = [0, $format, 'Disabled'];
|
||||
$data[] = [1, $format, 'Enabled'];
|
||||
|
||||
$format = ['format' => 'unicode-yes-no'];
|
||||
$data[] = [1, $format, '✔'];
|
||||
$data[] = [0, $format, '✖'];
|
||||
|
||||
$format = [
|
||||
'format' => 'custom',
|
||||
'format_custom_false' => 'FALSE',
|
||||
'format_custom_true' => 'TRUE',
|
||||
];
|
||||
$data[] = [0, $format, 'FALSE'];
|
||||
$data[] = [1, $format, 'TRUE'];
|
||||
|
||||
foreach ($data as $test_data) {
|
||||
[$value, $settings, $expected] = $test_data;
|
||||
|
||||
$component = $this->display->getComponent($this->fieldName);
|
||||
$component['settings'] = $settings;
|
||||
$this->display->setComponent($this->fieldName, $component);
|
||||
|
||||
$entity = EntityTest::create([]);
|
||||
$entity->{$this->fieldName}->value = $value;
|
||||
|
||||
// Verify that all HTML is escaped and newlines are retained.
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertRaw($expected);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Boolean;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the new entity API for the boolean field type.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BooleanItemTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a boolean field and storage for validation.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_boolean',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'boolean',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_boolean',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent('field_boolean', [
|
||||
'type' => 'boolean_checkbox',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using entity fields of the boolean field type.
|
||||
*/
|
||||
public function testBooleanItem(): void {
|
||||
// Verify entity creation.
|
||||
$entity = EntityTest::create();
|
||||
$value = '1';
|
||||
$entity->field_boolean = $value;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertInstanceOf(FieldItemListInterface::class, $entity->field_boolean);
|
||||
$this->assertInstanceOf(FieldItemInterface::class, $entity->field_boolean[0]);
|
||||
$this->assertEquals($value, $entity->field_boolean->value);
|
||||
$this->assertEquals($value, $entity->field_boolean[0]->value);
|
||||
|
||||
// Verify changing the boolean value.
|
||||
$new_value = 0;
|
||||
$entity->field_boolean->value = $new_value;
|
||||
$this->assertEquals($new_value, $entity->field_boolean->value);
|
||||
|
||||
// Read changed entity and assert changed values.
|
||||
$entity->save();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($new_value, $entity->field_boolean->value);
|
||||
|
||||
// Test sample item generation.
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_boolean->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
}
|
||||
531
web/core/modules/field/tests/src/Kernel/BulkDeleteTest.php
Normal file
531
web/core/modules/field/tests/src/Kernel/BulkDeleteTest.php
Normal file
@ -0,0 +1,531 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
|
||||
/**
|
||||
* Bulk delete storages and fields, and clean up afterwards.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class BulkDeleteTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* The fields to use in this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorages;
|
||||
|
||||
/**
|
||||
* The entities to use in this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entities;
|
||||
|
||||
/**
|
||||
* The entities to use in this test, keyed by bundle.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entitiesByBundles;
|
||||
|
||||
/**
|
||||
* The bundles for the entities used in this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $bundles;
|
||||
|
||||
/**
|
||||
* The entity type to be used in the test classes.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityTypeId = 'entity_test';
|
||||
|
||||
/**
|
||||
* Tests that the expected hooks have been invoked on the expected entities.
|
||||
*
|
||||
* @param string[] $expected_hooks
|
||||
* An array keyed by hook name, with one entry per expected invocation.
|
||||
* Each entry is the value of the "$entity" parameter the hook is expected
|
||||
* to have been passed.
|
||||
* @param array $actual_hooks
|
||||
* The array of actual hook invocations recorded by
|
||||
* FieldTestHelper::memorize().
|
||||
*/
|
||||
public function checkHooksInvocations($expected_hooks, $actual_hooks): void {
|
||||
foreach ($expected_hooks as $hook => $invocations) {
|
||||
$actual_invocations = $actual_hooks[$hook];
|
||||
|
||||
// Check that the number of invocations is correct.
|
||||
$this->assertSameSize($invocations, $actual_invocations, "$hook() was called the expected number of times.");
|
||||
|
||||
// Check that the hook was called for each expected argument.
|
||||
foreach ($invocations as $argument) {
|
||||
$found = FALSE;
|
||||
foreach ($actual_invocations as $actual_arguments) {
|
||||
// The argument we are looking for is either an array of entities as
|
||||
// the second argument or a single entity object as the first.
|
||||
if ($argument instanceof EntityInterface && $actual_arguments[0]->id() == $argument->id()) {
|
||||
$found = TRUE;
|
||||
break;
|
||||
}
|
||||
// In case of an array, compare the array size and make sure it
|
||||
// contains the same elements.
|
||||
elseif (is_array($argument) && count($actual_arguments[1]) == count($argument) && count(array_diff_key($actual_arguments[1], $argument)) == 0) {
|
||||
$found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$this->assertTrue($found, "$hook() was called on expected argument");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldStorages = [];
|
||||
$this->entities = [];
|
||||
$this->entitiesByBundles = [];
|
||||
|
||||
// Create two bundles.
|
||||
$this->bundles = ['bb_1' => 'bb_1', 'bb_2' => 'bb_2'];
|
||||
foreach ($this->bundles as $name => $desc) {
|
||||
EntityTestHelper::createBundle($name, $desc);
|
||||
}
|
||||
|
||||
// Create two field storages.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'bf_1',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$this->fieldStorages[] = $field_storage;
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'bf_2',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$this->fieldStorages[] = $field_storage;
|
||||
|
||||
// For each bundle, create each field, and 10 entities with values for the
|
||||
// fields.
|
||||
foreach ($this->bundles as $bundle) {
|
||||
foreach ($this->fieldStorages as $field_storage) {
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
])->save();
|
||||
}
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create(['type' => $bundle]);
|
||||
foreach ($this->fieldStorages as $field_storage) {
|
||||
$entity->{$field_storage->getName()}->setValue($this->_generateTestFieldValues($field_storage->getCardinality()));
|
||||
}
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
$this->entities = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)->loadMultiple();
|
||||
foreach ($this->entities as $entity) {
|
||||
// This test relies on the entities having stale field definitions
|
||||
// so that the deleted field can be accessed on them. Access the field
|
||||
// now, so that they are always loaded.
|
||||
$entity->bf_1->value;
|
||||
|
||||
// Also keep track of the entities per bundle.
|
||||
$this->entitiesByBundles[$entity->bundle()][$entity->id()] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting fields.
|
||||
*
|
||||
* Verify that deleting a field leaves the field data items in the database
|
||||
* and that the appropriate Field API functions can operate on the deleted
|
||||
* data and field definition.
|
||||
*
|
||||
* This tests how EntityFieldQuery interacts with field deletion and could be
|
||||
* moved to FieldCrudTestCase, but depends on this class's setUp().
|
||||
*/
|
||||
public function testDeleteField(): void {
|
||||
$bundle = reset($this->bundles);
|
||||
$field_storage = reset($this->fieldStorages);
|
||||
$field_name = $field_storage->getName();
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test');
|
||||
|
||||
// There are 10 entities of this bundle.
|
||||
$found = $storage
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $bundle)
|
||||
->execute();
|
||||
$this->assertCount(10, $found, 'Correct number of entities found before deleting');
|
||||
|
||||
// Delete the field.
|
||||
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
|
||||
$field->delete();
|
||||
|
||||
// The field still exists, deleted.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'field_storage_uuid' => $field_storage->uuid(),
|
||||
'deleted' => TRUE,
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(1, $fields, 'There is one deleted field');
|
||||
$field = $fields[$field->uuid()];
|
||||
$this->assertEquals($bundle, $field->getTargetBundle(), 'The deleted field is for the correct bundle');
|
||||
|
||||
// Check that the actual stored content did not change during delete.
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$table = $table_mapping->getDedicatedDataTableName($field_storage);
|
||||
$column = $table_mapping->getFieldColumnName($field_storage, 'value');
|
||||
$result = Database::getConnection()->select($table, 't')
|
||||
->fields('t')
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
$this->assertEquals($row->{$column}, $this->entities[$row->entity_id]->{$field_name}->value);
|
||||
}
|
||||
|
||||
// There are 0 entities of this bundle with non-deleted data.
|
||||
$found = $storage
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $bundle)
|
||||
->condition("$field_name.deleted", 0)
|
||||
->execute();
|
||||
$this->assertEmpty($found, 'No entities found after deleting');
|
||||
|
||||
// There are 10 entities of this bundle when deleted fields are allowed, and
|
||||
// their values are correct.
|
||||
$found = $storage
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $bundle)
|
||||
->condition("$field_name.deleted", 1)
|
||||
->sort('id')
|
||||
->execute();
|
||||
$this->assertCount(10, $found, 'Correct number of entities found after deleting');
|
||||
$this->assertEmpty(array_diff($found, array_keys($this->entities)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that recreating a field with the name as a deleted field works.
|
||||
*/
|
||||
public function testPurgeWithDeletedAndActiveField(): void {
|
||||
$bundle = reset($this->bundles);
|
||||
// Create another field storage.
|
||||
$field_name = 'bf_3';
|
||||
$deleted_field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$deleted_field_storage->save();
|
||||
// Create the field.
|
||||
FieldConfig::create([
|
||||
'field_storage' => $deleted_field_storage,
|
||||
'bundle' => $bundle,
|
||||
])->save();
|
||||
|
||||
for ($i = 0; $i < 20; $i++) {
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create(['type' => $bundle]);
|
||||
$entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
// Delete the field.
|
||||
$deleted_field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
|
||||
$deleted_field->delete();
|
||||
$deleted_field_uuid = $deleted_field->uuid();
|
||||
|
||||
// Reload the field storage.
|
||||
$field_storages = \Drupal::entityTypeManager()
|
||||
->getStorage('field_storage_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $deleted_field_storage->uuid(),
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$deleted_field_storage = reset($field_storages);
|
||||
|
||||
// Create the field again.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
])->save();
|
||||
|
||||
// The field still exists, deleted, with the same field name.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $deleted_field_uuid,
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertArrayHasKey($deleted_field_uuid, $fields);
|
||||
$this->assertTrue($fields[$deleted_field_uuid]->isDeleted());
|
||||
$this->assertSame($field_name, $fields[$deleted_field_uuid]->getName());
|
||||
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId)
|
||||
->create(['type' => $bundle]);
|
||||
$entity->{$field_name}->setValue($this->_generateTestFieldValues(1));
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
// Check that the two field storages have different tables.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$deleted_table_name = $table_mapping->getDedicatedDataTableName($deleted_field_storage, TRUE);
|
||||
$active_table_name = $table_mapping->getDedicatedDataTableName($field_storage);
|
||||
|
||||
field_purge_batch(50);
|
||||
|
||||
// Ensure the new field still has its table and the deleted one has been
|
||||
// removed.
|
||||
$this->assertTrue(\Drupal::database()->schema()->tableExists($active_table_name));
|
||||
$this->assertFalse(\Drupal::database()->schema()->tableExists($deleted_table_name));
|
||||
|
||||
// The field has been removed from the system.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'field_storage_uuid' => $deleted_field_storage->uuid(),
|
||||
'deleted' => TRUE,
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $fields, 'The field is gone');
|
||||
|
||||
// Verify there are still 10 entries in the main table.
|
||||
$count = \Drupal::database()
|
||||
->select('entity_test__' . $field_name, 'f')
|
||||
->fields('f', ['entity_id'])
|
||||
->condition('bundle', $bundle)
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals(10, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests purging fields.
|
||||
*
|
||||
* Verify that field data items and fields are purged when a field storage is
|
||||
* deleted.
|
||||
*/
|
||||
public function testPurgeField(): void {
|
||||
// Start recording hook invocations.
|
||||
FieldTestHelper::memorize();
|
||||
|
||||
$bundle = reset($this->bundles);
|
||||
$field_storage = reset($this->fieldStorages);
|
||||
$field_name = $field_storage->getName();
|
||||
|
||||
// Delete the field.
|
||||
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
|
||||
$field->delete();
|
||||
|
||||
// No field hooks were called.
|
||||
$mem = FieldTestHelper::memorize();
|
||||
$this->assertCount(0, $mem, 'No field hooks were called');
|
||||
|
||||
$batch_size = 2;
|
||||
for ($count = 8; $count >= 0; $count -= $batch_size) {
|
||||
// Purge two entities.
|
||||
field_purge_batch($batch_size);
|
||||
|
||||
// There are $count deleted entities left.
|
||||
$found = \Drupal::entityQuery('entity_test')
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $bundle)
|
||||
->condition($field_name . '.deleted', 1)
|
||||
->execute();
|
||||
$this->assertCount($count, $found, 'Correct number of entities found after purging 2');
|
||||
}
|
||||
|
||||
// Check hooks invocations.
|
||||
// FieldItemInterface::delete() should have been called once for each entity
|
||||
// in the bundle.
|
||||
$actual_hooks = FieldTestHelper::memorize();
|
||||
$hooks = [];
|
||||
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
||||
// The field still exists, deleted.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'field_storage_uuid' => $field_storage->uuid(),
|
||||
'deleted' => TRUE,
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(1, $fields, 'There is one deleted field');
|
||||
|
||||
// Purge the field.
|
||||
field_purge_batch($batch_size);
|
||||
|
||||
// The field is gone.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'field_storage_uuid' => $field_storage->uuid(),
|
||||
'deleted' => TRUE,
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $fields, 'The field is gone');
|
||||
|
||||
// The field storage still exists, not deleted, because it has a second
|
||||
// field.
|
||||
$storages = \Drupal::entityTypeManager()
|
||||
->getStorage('field_storage_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $field_storage->uuid(),
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertTrue(isset($storages[$field_storage->uuid()]), 'The field storage exists and is not deleted');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests purging field storages.
|
||||
*
|
||||
* Verify that field storages are preserved and purged correctly as multiple
|
||||
* fields are deleted and purged.
|
||||
*/
|
||||
public function testPurgeFieldStorage(): void {
|
||||
// Start recording hook invocations.
|
||||
FieldTestHelper::memorize();
|
||||
|
||||
$field_storage = reset($this->fieldStorages);
|
||||
$field_name = $field_storage->getName();
|
||||
|
||||
// Delete the first field.
|
||||
$bundle = reset($this->bundles);
|
||||
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
|
||||
$field->delete();
|
||||
|
||||
// Assert that FieldItemInterface::delete() was not called yet.
|
||||
$mem = FieldTestHelper::memorize();
|
||||
$this->assertCount(0, $mem, 'No field hooks were called.');
|
||||
|
||||
// Purge the data.
|
||||
field_purge_batch(10);
|
||||
|
||||
// Check hooks invocations.
|
||||
// FieldItemInterface::delete() should have been called once for each entity in the
|
||||
// bundle.
|
||||
$actual_hooks = FieldTestHelper::memorize();
|
||||
$hooks = [];
|
||||
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
||||
// The field still exists, deleted.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties(['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
|
||||
$this->assertArrayHasKey($field->uuid(), $fields);
|
||||
$this->assertTrue($fields[$field->uuid()]->isDeleted());
|
||||
|
||||
// Purge again to purge the field.
|
||||
field_purge_batch(0);
|
||||
|
||||
// The field is gone.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties(['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
|
||||
$this->assertCount(0, $fields, 'The field is purged.');
|
||||
// The field storage still exists, not deleted.
|
||||
$storages = \Drupal::entityTypeManager()
|
||||
->getStorage('field_storage_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $field_storage->uuid(),
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertArrayHasKey($field_storage->uuid(), $storages);
|
||||
$this->assertFalse($storages[$field_storage->uuid()]->isDeleted());
|
||||
|
||||
// Delete the second field.
|
||||
$bundle = next($this->bundles);
|
||||
$field = FieldConfig::loadByName($this->entityTypeId, $bundle, $field_name);
|
||||
$field->delete();
|
||||
|
||||
// Assert that FieldItemInterface::delete() was not called yet.
|
||||
$mem = FieldTestHelper::memorize();
|
||||
$this->assertCount(0, $mem, 'No field hooks were called.');
|
||||
|
||||
// Purge the data.
|
||||
field_purge_batch(10);
|
||||
|
||||
// Check hooks invocations (same as above, for the 2nd bundle).
|
||||
$actual_hooks = FieldTestHelper::memorize();
|
||||
$hooks = [];
|
||||
$hooks['field_test_field_delete'] = $this->entitiesByBundles[$bundle];
|
||||
$this->checkHooksInvocations($hooks, $actual_hooks);
|
||||
|
||||
// The field and the storage still exist, deleted.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties(['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
|
||||
$this->assertArrayHasKey($field->uuid(), $fields);
|
||||
$this->assertTrue($fields[$field->uuid()]->isDeleted());
|
||||
$storages = \Drupal::entityTypeManager()
|
||||
->getStorage('field_storage_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $field_storage->uuid(),
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertArrayHasKey($field_storage->uuid(), $storages);
|
||||
$this->assertTrue($storages[$field_storage->uuid()]->isDeleted());
|
||||
|
||||
// Purge again to purge the field and the storage.
|
||||
field_purge_batch(0);
|
||||
|
||||
// The field and the storage are gone.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties(['uuid' => $field->uuid(), 'include_deleted' => TRUE]);
|
||||
$this->assertCount(0, $fields, 'The field is purged.');
|
||||
$storages = \Drupal::entityTypeManager()
|
||||
->getStorage('field_storage_config')
|
||||
->loadByProperties([
|
||||
'uuid' => $field_storage->uuid(),
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $storages, 'The field storage is purged.');
|
||||
}
|
||||
|
||||
}
|
||||
114
web/core/modules/field/tests/src/Kernel/ConfigActionsTest.php
Normal file
114
web/core/modules/field/tests/src/Kernel/ConfigActionsTest.php
Normal file
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Config\Action\ConfigActionManager;
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* @group field
|
||||
*/
|
||||
class ConfigActionsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field', 'user'];
|
||||
|
||||
/**
|
||||
* The configuration manager.
|
||||
*/
|
||||
private readonly ConfigActionManager $configActionManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('entity_test_with_bundle');
|
||||
EntityTestBundle::create([
|
||||
'id' => 'test',
|
||||
'label' => $this->randomString(),
|
||||
])->save();
|
||||
|
||||
$this->configActionManager = $this->container->get('plugin.manager.config_action');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the application of configuration actions on field settings.
|
||||
*/
|
||||
public function testConfigActions(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'test',
|
||||
'type' => 'boolean',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
$this->assertFalse($field->isRequired());
|
||||
$this->assertSame('On', (string) $field->getSetting('on_label'));
|
||||
$this->assertSame('Off', (string) $field->getSetting('off_label'));
|
||||
$this->assertEmpty($field->getDefaultValueLiteral());
|
||||
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setLabel',
|
||||
$field->getConfigDependencyName(),
|
||||
'Not what you were expecting!',
|
||||
);
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setDescription',
|
||||
$field->getConfigDependencyName(),
|
||||
"Any ol' nonsense can go here.",
|
||||
);
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setTranslatable',
|
||||
$field->getConfigDependencyName(),
|
||||
FALSE,
|
||||
);
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setRequired',
|
||||
$field->getConfigDependencyName(),
|
||||
TRUE,
|
||||
);
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setSettings',
|
||||
$field->getConfigDependencyName(),
|
||||
[
|
||||
'on_label' => 'Zap!',
|
||||
'off_label' => 'Zing!',
|
||||
],
|
||||
);
|
||||
$this->configActionManager->applyAction(
|
||||
'entity_method:field.field:setDefaultValue',
|
||||
$field->getConfigDependencyName(),
|
||||
[
|
||||
'value' => FALSE,
|
||||
],
|
||||
);
|
||||
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertNotEmpty($field);
|
||||
$this->assertSame('Not what you were expecting!', $field->getLabel());
|
||||
$this->assertSame("Any ol' nonsense can go here.", $field->getDescription());
|
||||
$this->assertFalse($field->isTranslatable());
|
||||
$this->assertTrue($field->isRequired());
|
||||
$this->assertSame('Zap!', $field->getSetting('on_label'));
|
||||
$this->assertSame('Zing!', $field->getSetting('off_label'));
|
||||
$this->assertSame([['value' => 0]], $field->getDefaultValueLiteral());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Field\FieldDefinitionInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Tests exposing field definitions for configurable fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class ConfigFieldDefinitionTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $entityType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $bundle;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a field and a storage of type 'test_field', on the 'entity_test'
|
||||
// entity type.
|
||||
$this->entityType = 'entity_test';
|
||||
$this->bundle = 'entity_test';
|
||||
$this->createFieldWithStorage('', $this->entityType, $this->bundle);
|
||||
|
||||
// Create a second field on 'entity_test_rev'.
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->createFieldWithStorage('_rev', 'entity_test_rev', 'entity_test_rev');
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure a field definition is exposed for a configurable field.
|
||||
*/
|
||||
public function testBundleFieldDefinition(): void {
|
||||
$definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions($this->entityType, $this->bundle);
|
||||
$this->assertTrue(isset($definitions[$this->fieldTestData->field->getName()]));
|
||||
$this->assertInstanceOf(FieldDefinitionInterface::class, $definitions[$this->fieldTestData->field->getName()]);
|
||||
// Make sure fields on other entity types are not exposed.
|
||||
$this->assertFalse(isset($definitions[$this->fieldTestData->field_rev->getName()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure a field storage definition is exposed for a configurable field.
|
||||
*/
|
||||
public function testFieldStorageDefinition(): void {
|
||||
$field_storage_definitions = \Drupal::service('entity_field.manager')->getFieldStorageDefinitions($this->entityType);
|
||||
$this->assertTrue(isset($field_storage_definitions[$this->fieldTestData->field->getName()]));
|
||||
$this->assertInstanceOf(FieldStorageDefinitionInterface::class, $field_storage_definitions[$this->fieldTestData->field->getName()]);
|
||||
// Make sure storages on other entity types are not exposed.
|
||||
$this->assertFalse(isset($field_storage_definitions[$this->fieldTestData->field_rev->getName()]));
|
||||
}
|
||||
|
||||
}
|
||||
325
web/core/modules/field/tests/src/Kernel/DisplayApiTest.php
Normal file
325
web/core/modules/field/tests/src/Kernel/DisplayApiTest.php
Normal file
@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the field display API.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class DisplayApiTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* The field name to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The field label to use in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The field cardinality to use in this test.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cardinality;
|
||||
|
||||
/**
|
||||
* The field display options to use in this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $displayOptions;
|
||||
|
||||
/**
|
||||
* The test entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* An array of random values, in the format expected for field values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a field and its storage.
|
||||
$this->fieldName = 'test_field';
|
||||
$this->label = $this->randomMachineName();
|
||||
$this->cardinality = 4;
|
||||
|
||||
$field_storage = [
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => $this->cardinality,
|
||||
];
|
||||
$field = [
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'label' => $this->label,
|
||||
];
|
||||
|
||||
$this->displayOptions = [
|
||||
'default' => [
|
||||
'type' => 'field_test_default',
|
||||
'settings' => [
|
||||
'test_formatter_setting' => $this->randomMachineName(),
|
||||
],
|
||||
],
|
||||
'teaser' => [
|
||||
'type' => 'field_test_default',
|
||||
'settings' => [
|
||||
'test_formatter_setting' => $this->randomMachineName(),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
FieldConfig::create($field)->save();
|
||||
// Create a display for the default view mode.
|
||||
$display_repository->getViewDisplay($field['entity_type'], $field['bundle'])
|
||||
->setComponent($this->fieldName, $this->displayOptions['default'])
|
||||
->save();
|
||||
// Create a display for the teaser view mode.
|
||||
EntityViewMode::create([
|
||||
'id' => 'entity_test.teaser',
|
||||
'label' => 'Teaser',
|
||||
'targetEntityType' => 'entity_test',
|
||||
])->save();
|
||||
$display_repository->getViewDisplay($field['entity_type'], $field['bundle'], 'teaser')
|
||||
->setComponent($this->fieldName, $this->displayOptions['teaser'])
|
||||
->save();
|
||||
|
||||
// Create an entity with values.
|
||||
$this->values = $this->_generateTestFieldValues($this->cardinality);
|
||||
$this->entity = EntityTest::create();
|
||||
$this->entity->{$this->fieldName}->setValue($this->values);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the FieldItemListInterface::view() method.
|
||||
*/
|
||||
public function testFieldItemListView(): void {
|
||||
$items = $this->entity->get($this->fieldName);
|
||||
|
||||
\Drupal::service('theme_installer')->install(['stark']);
|
||||
$this->config('system.theme')->set('default', 'stark')->save();
|
||||
|
||||
// No display settings: check that default display settings are used.
|
||||
$build = $items->view();
|
||||
$this->render($build);
|
||||
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
|
||||
$setting = $settings['test_formatter_setting'];
|
||||
$this->assertText($this->label, 'Label was displayed.');
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// Display settings: Check hidden field.
|
||||
$display = [
|
||||
'label' => 'hidden',
|
||||
'type' => 'field_test_multiple',
|
||||
'settings' => [
|
||||
'test_formatter_setting_multiple' => $this->randomMachineName(),
|
||||
'alter' => TRUE,
|
||||
],
|
||||
];
|
||||
$build = $items->view($display);
|
||||
$this->render($build);
|
||||
$setting = $display['settings']['test_formatter_setting_multiple'];
|
||||
$this->assertNoText($this->label, 'Label was not displayed.');
|
||||
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
|
||||
$this->assertText('entity language is en', 'Language is placed onto the context.');
|
||||
$array = [];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$array[] = $delta . ':' . $value['value'];
|
||||
}
|
||||
$this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.');
|
||||
|
||||
// Display settings: Check visually_hidden field.
|
||||
$display = [
|
||||
'label' => 'visually_hidden',
|
||||
'type' => 'field_test_multiple',
|
||||
'settings' => [
|
||||
'test_formatter_setting_multiple' => $this->randomMachineName(),
|
||||
'alter' => TRUE,
|
||||
],
|
||||
];
|
||||
$build = $items->view($display);
|
||||
$this->render($build);
|
||||
$setting = $display['settings']['test_formatter_setting_multiple'];
|
||||
$this->assertRaw('visually-hidden', 'Label was visually hidden.');
|
||||
$this->assertText('field_test_entity_display_build_alter', 'Alter fired, display passed.');
|
||||
$this->assertText('entity language is en', 'Language is placed onto the context.');
|
||||
$array = [];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$array[] = $delta . ':' . $value['value'];
|
||||
}
|
||||
$this->assertText($setting . '|' . implode('|', $array), 'Values were displayed with expected setting.');
|
||||
|
||||
// Check the prepare_view steps are invoked.
|
||||
$display = [
|
||||
'label' => 'hidden',
|
||||
'type' => 'field_test_with_prepare_view',
|
||||
'settings' => [
|
||||
'test_formatter_setting_additional' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
$build = $items->view($display);
|
||||
$this->render($build);
|
||||
$setting = $display['settings']['test_formatter_setting_additional'];
|
||||
$this->assertNoText($this->label, 'Label was not displayed.');
|
||||
$this->assertNoText('field_test_entity_display_build_alter', 'Alter not fired.');
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// View mode: check that display settings specified in the display object
|
||||
// are used.
|
||||
$build = $items->view('teaser');
|
||||
$this->render($build);
|
||||
$setting = $this->displayOptions['teaser']['settings']['test_formatter_setting'];
|
||||
$this->assertText($this->label, 'Label was displayed.');
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// Unknown view mode: check that display settings for 'default' view mode
|
||||
// are used.
|
||||
$build = $items->view('unknown_view_mode');
|
||||
$this->render($build);
|
||||
$setting = $this->displayOptions['default']['settings']['test_formatter_setting'];
|
||||
$this->assertText($this->label, 'Label was displayed.');
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the FieldItemInterface::view() method.
|
||||
*/
|
||||
public function testFieldItemView(): void {
|
||||
// No display settings: check that default display settings are used.
|
||||
$settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('field_test_default');
|
||||
$setting = $settings['test_formatter_setting'];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$item = $this->entity->{$this->fieldName}[$delta];
|
||||
$build = $item->view();
|
||||
$this->render($build);
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// Check that explicit display settings are used.
|
||||
$display = [
|
||||
'type' => 'field_test_multiple',
|
||||
'settings' => [
|
||||
'test_formatter_setting_multiple' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
$setting = $display['settings']['test_formatter_setting_multiple'];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$item = $this->entity->{$this->fieldName}[$delta];
|
||||
$build = $item->view($display);
|
||||
$this->render($build);
|
||||
$this->assertText($setting . '|0:' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// Check that prepare_view steps are invoked.
|
||||
$display = [
|
||||
'type' => 'field_test_with_prepare_view',
|
||||
'settings' => [
|
||||
'test_formatter_setting_additional' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
$setting = $display['settings']['test_formatter_setting_additional'];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$item = $this->entity->{$this->fieldName}[$delta];
|
||||
$build = $item->view($display);
|
||||
$this->render($build);
|
||||
$this->assertText($setting . '|' . $value['value'] . '|' . ($value['value'] + 1), "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// View mode: check that display settings specified in the field are used.
|
||||
$setting = $this->displayOptions['teaser']['settings']['test_formatter_setting'];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$item = $this->entity->{$this->fieldName}[$delta];
|
||||
$build = $item->view('teaser');
|
||||
$this->render($build);
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
|
||||
// Unknown view mode: check that display settings for 'default' view mode
|
||||
// are used.
|
||||
$setting = $this->displayOptions['default']['settings']['test_formatter_setting'];
|
||||
foreach ($this->values as $delta => $value) {
|
||||
$item = $this->entity->{$this->fieldName}[$delta];
|
||||
$build = $item->view('unknown_view_mode');
|
||||
$this->render($build);
|
||||
$this->assertText($setting . '|' . $value['value'], "Value $delta was displayed with expected setting.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the prepareView() formatter method still fires for empty values.
|
||||
*/
|
||||
public function testFieldEmpty(): void {
|
||||
// Uses
|
||||
// \Drupal\field_test\Plugin\Field\FieldFormatter\TestFieldEmptyFormatter.
|
||||
$display = [
|
||||
'label' => 'hidden',
|
||||
'type' => 'field_empty_test',
|
||||
'settings' => [
|
||||
'test_empty_string' => '**EMPTY FIELD**' . $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
// $this->entity is set by the setUp() method and by default contains 4
|
||||
// numeric values. We only want to test the display of this one field.
|
||||
$build = $this->entity->get($this->fieldName)->view($display);
|
||||
$this->render($build);
|
||||
// The test field by default contains values, so should not display the
|
||||
// default "empty" text.
|
||||
$this->assertNoText($display['settings']['test_empty_string']);
|
||||
|
||||
// Now remove the values from the test field and retest.
|
||||
$this->entity->{$this->fieldName} = [];
|
||||
$this->entity->save();
|
||||
$build = $this->entity->get($this->fieldName)->view($display);
|
||||
$this->render($build);
|
||||
// This time, as the field values have been removed, we *should* show the
|
||||
// default "empty" text.
|
||||
$this->assertText($display['settings']['test_empty_string']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Ensure display modes are updated when fields are created.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class DisplayModeUpdateTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* The default view display name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultViewDisplayName;
|
||||
|
||||
/**
|
||||
* The default form display name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $defaultFormDisplayName;
|
||||
|
||||
/**
|
||||
* The alternate view display name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foobarViewDisplayName;
|
||||
|
||||
/**
|
||||
* The alternate form display name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $foobarFormDisplayName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create 'default' view-display.
|
||||
$default_view_display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$default_view_display->save();
|
||||
$this->defaultViewDisplayName = $default_view_display->getConfigDependencyName();
|
||||
|
||||
// Create 'default' form-display.
|
||||
$default_form_display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$default_form_display->save();
|
||||
$this->defaultFormDisplayName = $default_form_display->getConfigDependencyName();
|
||||
|
||||
// Create a view-mode 'foobar', create view-display that uses it.
|
||||
EntityViewMode::create([
|
||||
'id' => 'entity_test.foobar',
|
||||
'label' => 'Foobar',
|
||||
'targetEntityType' => 'entity_test',
|
||||
'status' => TRUE,
|
||||
'enabled' => TRUE,
|
||||
])->save();
|
||||
$foobar_view_display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'foobar',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$foobar_view_display->save();
|
||||
$this->foobarViewDisplayName = $foobar_view_display->getConfigDependencyName();
|
||||
|
||||
// Create a new form-mode 'foobar', create form-display that uses it.
|
||||
EntityFormMode::create([
|
||||
'id' => 'entity_test.foobar',
|
||||
'label' => 'Foobar',
|
||||
'targetEntityType' => 'entity_test',
|
||||
'status' => TRUE,
|
||||
'enabled' => TRUE,
|
||||
])->save();
|
||||
$foobar_form_display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'foobar',
|
||||
'status' => TRUE,
|
||||
]);
|
||||
$foobar_form_display->save();
|
||||
$this->foobarFormDisplayName = $foobar_form_display->getConfigDependencyName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure display modes are updated when fields are created.
|
||||
*/
|
||||
public function testDisplayModeUpdateAfterFieldCreation(): void {
|
||||
|
||||
// Sanity test: field has not been created yet, so should not exist in
|
||||
// display.
|
||||
$this->assertArrayNotHasKey('field_test', \Drupal::config($this->defaultViewDisplayName)->get('hidden'));
|
||||
$this->assertArrayNotHasKey('field_test', \Drupal::config($this->defaultFormDisplayName)->get('hidden'));
|
||||
$this->assertArrayNotHasKey('field_test', \Drupal::config($this->foobarViewDisplayName)->get('hidden'));
|
||||
$this->assertArrayNotHasKey('field_test', \Drupal::config($this->foobarFormDisplayName)->get('hidden'));
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Ensure field is added to display modes.
|
||||
$this->assertArrayHasKey('field_test', \Drupal::config($this->defaultViewDisplayName)->get('hidden'));
|
||||
$this->assertArrayHasKey('field_test', \Drupal::config($this->defaultFormDisplayName)->get('hidden'));
|
||||
$this->assertArrayHasKey('field_test', \Drupal::config($this->foobarViewDisplayName)->get('hidden'));
|
||||
$this->assertArrayHasKey('field_test', \Drupal::config($this->foobarFormDisplayName)->get('hidden'));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Email;
|
||||
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the new entity API for the email field type.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class EmailItemTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create an email field storage and field for validation.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_email',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'email',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_email',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a form display for the default form mode.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('entity_test', 'entity_test')
|
||||
->setComponent('field_email', [
|
||||
'type' => 'email_default',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using entity fields of the email field type.
|
||||
*/
|
||||
public function testEmailItem(): void {
|
||||
// Verify entity creation.
|
||||
$entity = EntityTest::create();
|
||||
$value = 'test@example.com';
|
||||
$entity->field_email = $value;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertInstanceOf(FieldItemListInterface::class, $entity->field_email);
|
||||
$this->assertInstanceOf(FieldItemInterface::class, $entity->field_email[0]);
|
||||
$this->assertEquals($value, $entity->field_email->value);
|
||||
$this->assertEquals($value, $entity->field_email[0]->value);
|
||||
|
||||
// Verify changing the email value.
|
||||
$new_value = $this->randomMachineName();
|
||||
$entity->field_email->value = $new_value;
|
||||
$this->assertEquals($new_value, $entity->field_email->value);
|
||||
|
||||
// Read changed entity and assert changed values.
|
||||
$entity->save();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($new_value, $entity->field_email->value);
|
||||
|
||||
// Test sample item generation.
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_email->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests validation of field_config entities.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldConfigValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
use ContentTypeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'node', 'entity_test', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig('node');
|
||||
$this->createContentType(['type' => 'one']);
|
||||
$this->createContentType(['type' => 'another']);
|
||||
|
||||
EntityTestBundle::create(['id' => 'one'])->save();
|
||||
EntityTestBundle::create(['id' => 'another'])->save();
|
||||
|
||||
$this->entity = FieldConfig::loadByName('node', 'one', 'body');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that validation fails if config dependencies are invalid.
|
||||
*/
|
||||
public function testInvalidDependencies(): void {
|
||||
// Remove the config dependencies from the field entity.
|
||||
$dependencies = $this->entity->getDependencies();
|
||||
$dependencies['config'] = [];
|
||||
$this->entity->set('dependencies', $dependencies);
|
||||
|
||||
$this->assertValidationErrors(['' => 'This field requires a field storage.']);
|
||||
|
||||
// Things look sort-of like `field.storage.*.*` should fail validation
|
||||
// because they don't exist.
|
||||
$dependencies['config'] = [
|
||||
'field.storage.fake',
|
||||
'field.storage.',
|
||||
'field.storage.user.',
|
||||
];
|
||||
$this->entity->set('dependencies', $dependencies);
|
||||
$this->assertValidationErrors([
|
||||
'dependencies.config.0' => "The 'field.storage.fake' config does not exist.",
|
||||
'dependencies.config.1' => "The 'field.storage.' config does not exist.",
|
||||
'dependencies.config.2' => "The 'field.storage.user.' config does not exist.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation of a field_config's default value.
|
||||
*/
|
||||
public function testMultilineTextFieldDefaultValue(): void {
|
||||
$this->installEntitySchema('user');
|
||||
// First, create a field storage for which a complex default value exists.
|
||||
$this->enableModules(['text']);
|
||||
$text_field_storage_config = FieldStorageConfig::create([
|
||||
'type' => 'text_with_summary',
|
||||
'field_name' => 'novel',
|
||||
'entity_type' => 'user',
|
||||
]);
|
||||
$text_field_storage_config->save();
|
||||
|
||||
$this->entity = FieldConfig::create([
|
||||
'field_storage' => $text_field_storage_config,
|
||||
'bundle' => 'user',
|
||||
'default_value' => [
|
||||
0 => [
|
||||
'value' => "Multi\nLine",
|
||||
'summary' => '',
|
||||
'format' => 'basic_html',
|
||||
],
|
||||
],
|
||||
'dependencies' => [
|
||||
'config' => [
|
||||
$text_field_storage_config->getConfigDependencyName(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertValidationErrors([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the target bundle of the field is checked.
|
||||
*/
|
||||
public function testTargetBundleMustExist(): void {
|
||||
$this->entity->set('bundle', 'nope');
|
||||
$this->assertValidationErrors([
|
||||
'' => "The 'bundle' property cannot be changed.",
|
||||
'bundle' => "The 'nope' bundle does not exist on the 'node' entity type.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
// If we don't clear the previous settings here, we will get unrelated
|
||||
// validation errors (in addition to the one we're expecting), because the
|
||||
// settings from the *old* field_type won't match the config schema for the
|
||||
// settings of the *new* field_type.
|
||||
$this->entity->set('settings', []);
|
||||
parent::testImmutableProperties([
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'bundle' => 'another',
|
||||
'field_type' => 'string',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testRequiredPropertyKeysMissing(?array $additional_expected_validation_errors_when_missing = NULL): void {
|
||||
parent::testRequiredPropertyKeysMissing([
|
||||
'dependencies' => [
|
||||
// @see ::testInvalidDependencies()
|
||||
// @see \Drupal\Core\Config\Plugin\Validation\Constraint\RequiredConfigDependenciesConstraintValidator
|
||||
'' => 'This field requires a field storage.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testRequiredPropertyValuesMissing(?array $additional_expected_validation_errors_when_missing = NULL): void {
|
||||
parent::testRequiredPropertyValuesMissing([
|
||||
'dependencies' => [
|
||||
// @see ::testInvalidDependencies()
|
||||
// @see \Drupal\Core\Config\Plugin\Validation\Constraint\RequiredConfigDependenciesConstraintValidator
|
||||
'' => 'This field requires a field storage.',
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the field type plugin's existence is validated.
|
||||
*/
|
||||
public function testFieldTypePluginIsValidated(): void {
|
||||
// The `field_type` property is immutable, so we need to clone the entity in
|
||||
// order to cleanly change its immutable properties.
|
||||
$this->entity = $this->entity->createDuplicate()
|
||||
// We need to clear the current settings, or we will get validation errors
|
||||
// because the old settings are not supported by the new field type.
|
||||
->set('settings', [])
|
||||
->set('field_type', 'invalid');
|
||||
|
||||
$this->assertValidationErrors([
|
||||
'field_type' => "The 'invalid' plugin does not exist.",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that entity reference selection handler plugin IDs are validated.
|
||||
*/
|
||||
public function testEntityReferenceSelectionHandlerIsValidated(): void {
|
||||
$this->container->get('state')
|
||||
->set('field_test_disable_broken_entity_reference_handler', TRUE);
|
||||
$this->enableModules(['field_test']);
|
||||
|
||||
// The `field_type` property is immutable, so we need to clone the entity in
|
||||
// order to cleanly change its immutable properties.
|
||||
$this->entity = $this->entity->createDuplicate()
|
||||
->set('field_type', 'entity_reference')
|
||||
->set('settings', ['handler' => 'non_existent']);
|
||||
|
||||
$this->assertValidationErrors([
|
||||
'settings.handler' => "The 'non_existent' plugin does not exist.",
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Entity;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the ways that field entities handle their settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldEntitySettingsTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_with_bundle');
|
||||
EntityTestBundle::create(['id' => 'test', 'label' => 'Test'])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testFieldEntitiesCarryDefaultSettings(): void {
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'type' => 'integer',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'field_name' => 'test',
|
||||
]);
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'test',
|
||||
]);
|
||||
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $plugin_manager */
|
||||
$plugin_manager = $this->container->get('plugin.manager.field.field_type');
|
||||
$default_storage_settings = $plugin_manager->getDefaultStorageSettings('integer');
|
||||
$default_field_settings = $plugin_manager->getDefaultFieldSettings('integer');
|
||||
|
||||
// Both entities should have the complete, default settings for their
|
||||
// field type.
|
||||
$this->assertSame($default_storage_settings, $field_storage->get('settings'));
|
||||
$this->assertSame($default_field_settings, $field->get('settings'));
|
||||
|
||||
// If we try to set incomplete settings, the existing values should be
|
||||
// retained.
|
||||
$storage_settings = $field_storage->setSettings(['size' => 'big'])
|
||||
->get('settings');
|
||||
// There should be no missing settings.
|
||||
$missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
|
||||
$this->assertEmpty($missing_storage_settings);
|
||||
// The value we set should be remembered.
|
||||
$this->assertSame('big', $storage_settings['size']);
|
||||
|
||||
$field_settings = $field->setSetting('min', 10)->getSettings();
|
||||
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
|
||||
$this->assertEmpty($missing_field_settings);
|
||||
$this->assertSame(10, $field_settings['min']);
|
||||
|
||||
$field_settings = $field->setSettings(['max' => 39])->get('settings');
|
||||
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
|
||||
$this->assertEmpty($missing_field_settings);
|
||||
$this->assertSame(39, $field_settings['max']);
|
||||
|
||||
// Test that saving settings with incomplete settings is not triggering
|
||||
// error, and values are retained.
|
||||
$field_storage->save();
|
||||
$missing_storage_settings = array_diff_key($default_storage_settings, $storage_settings);
|
||||
$this->assertEmpty($missing_storage_settings);
|
||||
// The value we set should be remembered.
|
||||
$this->assertSame('big', $storage_settings['size']);
|
||||
|
||||
$field->save();
|
||||
$missing_field_settings = array_diff_key($default_field_settings, $field_settings);
|
||||
$this->assertEmpty($missing_field_settings);
|
||||
$this->assertSame(39, $field_settings['max']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity reference settings are normalized on field creation and save.
|
||||
*/
|
||||
public function testEntityReferenceSettingsNormalized(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'test_reference',
|
||||
'type' => 'entity_reference',
|
||||
'entity_type' => 'entity_test_with_bundle',
|
||||
'cardinality' => 1,
|
||||
'settings' => [
|
||||
'target_type' => 'entity_test_with_bundle',
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'test',
|
||||
'label' => 'Test Reference',
|
||||
'settings' => [
|
||||
'handler' => 'default',
|
||||
],
|
||||
]);
|
||||
$this->assertSame('default:entity_test_with_bundle', $field->getSetting('handler'));
|
||||
// If the handler is changed, it should be normalized again on pre-save.
|
||||
$field->setSetting('handler', 'default')->save();
|
||||
$this->assertSame('default:entity_test_with_bundle', $field->getSetting('handler'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Entity;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
|
||||
|
||||
/**
|
||||
* Tests validation of field_storage_config entities.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldStorageConfigValidationTest extends ConfigEntityValidationTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'node', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$this->entity = FieldStorageConfig::create([
|
||||
'type' => 'boolean',
|
||||
'field_name' => 'test',
|
||||
'entity_type' => 'user',
|
||||
]);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function testImmutableProperties(array $valid_values = []): void {
|
||||
$valid_values['type'] = 'string';
|
||||
parent::testImmutableProperties($valid_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the field type plugin's existence is validated.
|
||||
*/
|
||||
public function testFieldTypePluginIsValidated(): void {
|
||||
// The `type` property is immutable, so we need to clone the entity in
|
||||
// order to cleanly change its immutable properties.
|
||||
$this->entity = $this->entity->createDuplicate()
|
||||
->set('type', 'invalid');
|
||||
|
||||
$this->assertValidationErrors([
|
||||
'type' => "The 'invalid' plugin does not exist.",
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Entity\Update;
|
||||
|
||||
use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
|
||||
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\system\Functional\Entity\Traits\EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* Tests that schema changes in fields with data are detected during updates.
|
||||
*
|
||||
* @group Entity
|
||||
*/
|
||||
class SqlContentEntityStorageSchemaColumnTest extends KernelTestBase {
|
||||
|
||||
use EntityDefinitionTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['entity_test', 'field', 'text', 'user'];
|
||||
|
||||
/**
|
||||
* The created entity.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* The field.
|
||||
*
|
||||
* @var \Drupal\field\FieldConfigInterface
|
||||
*/
|
||||
protected $field;
|
||||
|
||||
/**
|
||||
* The field storage.
|
||||
*
|
||||
* @var \Drupal\field\FieldStorageConfigInterface
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The entity definition update manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
|
||||
*/
|
||||
protected EntityDefinitionUpdateManagerInterface $entityDefinitionUpdateManager;
|
||||
|
||||
/**
|
||||
* The state object.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected StateInterface $state;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
$field_name = 'test';
|
||||
$this->fieldStorage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'type' => 'string',
|
||||
'cardinality' => 1,
|
||||
]);
|
||||
$this->fieldStorage->save();
|
||||
|
||||
$this->field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test_rev',
|
||||
'bundle' => 'entity_test_rev',
|
||||
'required' => TRUE,
|
||||
]);
|
||||
$this->field->save();
|
||||
|
||||
// Create an entity with field data.
|
||||
$this->entity = EntityTestRev::create([
|
||||
'user_id' => 2,
|
||||
'name' => $this->randomMachineName(),
|
||||
$field_name => $this->randomString(),
|
||||
]);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that column-level schema changes are detected for fields with data.
|
||||
*/
|
||||
public function testColumnUpdate(): void {
|
||||
// Change the field type in the stored schema.
|
||||
$schema = \Drupal::keyValue('entity.storage_schema.sql')->get('entity_test_rev.field_schema_data.test');
|
||||
$schema['entity_test_rev__test']['fields']['test_value']['type'] = 'varchar_ascii';
|
||||
\Drupal::keyValue('entity.storage_schema.sql')->set('entity_test_rev.field_schema_data.test', $schema);
|
||||
|
||||
// Now attempt to run automatic updates. An exception should be thrown
|
||||
// since there is data in the table.
|
||||
$this->expectException(FieldStorageDefinitionUpdateForbiddenException::class);
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('test', 'entity_test_rev');
|
||||
$entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that schema changes are updated for fields with data with the flag.
|
||||
*/
|
||||
public function testColumnUpdateWithFlag(): void {
|
||||
// Change the field type in the stored schema.
|
||||
$schema = \Drupal::keyValue('entity.storage_schema.sql')->get('entity_test_rev.field_schema_data.test');
|
||||
$schema['entity_test_rev__test']['fields']['test_value']['type'] = 'varchar_ascii';
|
||||
\Drupal::keyValue('entity.storage_schema.sql')->set('entity_test_rev.field_schema_data.test', $schema);
|
||||
|
||||
// Now attempt to run automatic updates. It should succeed if the
|
||||
// column_changes_handled flag is passed.
|
||||
$entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_storage_definition = $entity_definition_update_manager->getFieldStorageDefinition('test', 'entity_test_rev');
|
||||
// Provide the flag to allow schema updates.
|
||||
$field_storage_definition->setSetting('column_changes_handled', TRUE);
|
||||
$entity_definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,499 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\EntityReference;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldFormatter\EntityReferenceEntityFormatter;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\filter\Entity\FilterFormat;
|
||||
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\user\RoleInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestLabel;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the formatters functionality.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceFormatterTest extends EntityKernelTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* The entity type used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType = 'entity_test';
|
||||
|
||||
/**
|
||||
* The bundle used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle = 'entity_test';
|
||||
|
||||
/**
|
||||
* The name of the field used in this test.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName = 'field_test';
|
||||
|
||||
/**
|
||||
* The entity to be referenced in this test.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $referencedEntity;
|
||||
|
||||
/**
|
||||
* An entity that is not yet saved to its persistent storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
protected $unsavedReferencedEntity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Use Stark theme for testing markup output.
|
||||
\Drupal::service('theme_installer')->install(['stark']);
|
||||
$this->config('system.theme')->set('default', 'stark')->save();
|
||||
$this->installEntitySchema('entity_test');
|
||||
// Grant the 'view test entity' permission.
|
||||
$this->installConfig(['user']);
|
||||
Role::load(RoleInterface::ANONYMOUS_ID)
|
||||
->grantPermission('view test entity')
|
||||
->save();
|
||||
|
||||
$this->createEntityReferenceField($this->entityType, $this->bundle, $this->fieldName, 'Field test', $this->entityType, 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
|
||||
// Set up a field, so that the entity that'll be referenced bubbles up a
|
||||
// cache tag when rendering it entirely.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'body',
|
||||
'entity_type' => $this->entityType,
|
||||
'type' => 'text',
|
||||
'settings' => [],
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $this->entityType,
|
||||
'bundle' => $this->bundle,
|
||||
'field_name' => 'body',
|
||||
'label' => 'Body',
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent('body', [
|
||||
'type' => 'text_default',
|
||||
'settings' => [],
|
||||
])
|
||||
->save();
|
||||
|
||||
FilterFormat::create([
|
||||
'format' => 'full_html',
|
||||
'name' => 'Full HTML',
|
||||
])->save();
|
||||
|
||||
// Create the entity to be referenced.
|
||||
$this->referencedEntity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityType)
|
||||
->create(['name' => $this->randomMachineName()]);
|
||||
$this->referencedEntity->body = [
|
||||
'value' => '<p>Hello, world!</p>',
|
||||
'format' => 'full_html',
|
||||
];
|
||||
$this->referencedEntity->save();
|
||||
|
||||
// Create another entity to be referenced but do not save it.
|
||||
$this->unsavedReferencedEntity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityType)
|
||||
->create(['name' => $this->randomMachineName()]);
|
||||
$this->unsavedReferencedEntity->body = [
|
||||
'value' => '<p>Hello, unsaved world!</p>',
|
||||
'format' => 'full_html',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert inaccessible items don't change the data of the fields.
|
||||
*/
|
||||
public function testAccess(): void {
|
||||
// Revoke the 'view test entity' permission for this test.
|
||||
Role::load(RoleInterface::ANONYMOUS_ID)
|
||||
->revokePermission('view test entity')
|
||||
->save();
|
||||
|
||||
$field_name = $this->fieldName;
|
||||
|
||||
$referencing_entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityType)
|
||||
->create(['name' => $this->randomMachineName()]);
|
||||
$referencing_entity->save();
|
||||
$referencing_entity->{$field_name}->entity = $this->referencedEntity;
|
||||
|
||||
// Assert user doesn't have access to the entity.
|
||||
$this->assertFalse($this->referencedEntity->access('view'), 'Current user does not have access to view the referenced entity.');
|
||||
|
||||
$formatter_manager = $this->container->get('plugin.manager.field.formatter');
|
||||
|
||||
// Get all the existing formatters.
|
||||
foreach ($formatter_manager->getOptions('entity_reference') as $formatter => $name) {
|
||||
// Set formatter type for the 'full' view mode.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($field_name, [
|
||||
'type' => $formatter,
|
||||
])
|
||||
->save();
|
||||
|
||||
// Invoke entity view.
|
||||
\Drupal::entityTypeManager()
|
||||
->getViewBuilder($referencing_entity->getEntityTypeId())
|
||||
->view($referencing_entity, 'default');
|
||||
|
||||
// Verify the un-accessible item still exists.
|
||||
$this->assertEquals($this->referencedEntity->id(), $referencing_entity->{$field_name}->target_id, "The un-accessible item still exists after $name formatter was executed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the merging of cache metadata.
|
||||
*/
|
||||
public function testCustomCacheTagFormatter(): void {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$formatter = 'entity_reference_custom_cache_tag';
|
||||
$build = $this->buildRenderArray([$this->referencedEntity], $formatter);
|
||||
|
||||
$renderer->renderRoot($build);
|
||||
$this->assertContains('custom_cache_tag', $build['#cache']['tags']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ID formatter.
|
||||
*/
|
||||
public function testIdFormatter(): void {
|
||||
$formatter = 'entity_reference_entity_id';
|
||||
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
|
||||
|
||||
$this->assertEquals($this->referencedEntity->id(), $build[0]['#plain_text'], sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
|
||||
$this->assertEquals($this->referencedEntity->getCacheTags(), $build[0]['#cache']['tags'], sprintf('The %s formatter has the expected cache tags.', $formatter));
|
||||
$this->assertTrue(!isset($build[1]), sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the entity formatter.
|
||||
*/
|
||||
public function testEntityFormatter(): void {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$formatter = 'entity_reference_entity_view';
|
||||
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
|
||||
|
||||
// Test the first field item.
|
||||
$expected_rendered_name_field_1 = '
|
||||
<div>' . $this->referencedEntity->label() . '</div>
|
||||
';
|
||||
$expected_rendered_body_field_1 = '
|
||||
<div>
|
||||
<div>Body</div>
|
||||
<div><p>Hello, world!</p></div>
|
||||
</div>
|
||||
';
|
||||
$renderer->renderRoot($build[0]);
|
||||
$this->assertSame('default | ' . $this->referencedEntity->label() . $expected_rendered_name_field_1 . $expected_rendered_body_field_1, (string) $build[0]['#markup'], sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
|
||||
$expected_cache_tags = Cache::mergeTags(\Drupal::entityTypeManager()->getViewBuilder($this->entityType)->getCacheTags(), $this->referencedEntity->getCacheTags());
|
||||
$expected_cache_tags = Cache::mergeTags($expected_cache_tags, FilterFormat::load('full_html')->getCacheTags());
|
||||
$this->assertEquals($expected_cache_tags, $build[0]['#cache']['tags'], "The $formatter formatter has the expected cache tags.");
|
||||
|
||||
// Test the second field item.
|
||||
$expected_rendered_name_field_2 = '
|
||||
<div>' . $this->unsavedReferencedEntity->label() . '</div>
|
||||
';
|
||||
$expected_rendered_body_field_2 = '
|
||||
<div>
|
||||
<div>Body</div>
|
||||
<div><p>Hello, unsaved world!</p></div>
|
||||
</div>
|
||||
';
|
||||
|
||||
$renderer->renderRoot($build[1]);
|
||||
$this->assertSame('default | ' . $this->unsavedReferencedEntity->label() . $expected_rendered_name_field_2 . $expected_rendered_body_field_2, (string) $build[1]['#markup'], sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the recursive rendering protection of the entity formatter.
|
||||
*/
|
||||
public function testEntityFormatterRecursiveRendering(): void {
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$formatter = 'entity_reference_entity_view';
|
||||
$view_builder = $this->entityTypeManager->getViewBuilder($this->entityType);
|
||||
|
||||
// Set the default view mode to use the 'entity_reference_entity_view'
|
||||
// formatter.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => $formatter,
|
||||
])
|
||||
->save();
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityType);
|
||||
$referencing_entity_1 = $storage->create(['name' => $this->randomMachineName()]);
|
||||
$referencing_entity_1->save();
|
||||
|
||||
// Create a self-reference.
|
||||
$referencing_entity_1->{$this->fieldName}->entity = $referencing_entity_1;
|
||||
$referencing_entity_1->save();
|
||||
|
||||
// Check that the recursive rendering stops after it reaches the specified
|
||||
// limit.
|
||||
$build = $view_builder->view($referencing_entity_1, 'default');
|
||||
$output = (string) $renderer->renderRoot($build);
|
||||
|
||||
// The title of entity_test entities is printed twice by default, so we have
|
||||
// to multiply the formatter's recursive rendering protection limit by 2.
|
||||
// Additionally, we have to take into account 2 additional occurrences of
|
||||
// the entity title because we're rendering the full entity, not just the
|
||||
// reference field.
|
||||
$expected_occurrences = EntityReferenceEntityFormatter::RECURSIVE_RENDER_LIMIT * 2 + 2;
|
||||
$actual_occurrences = substr_count($output, $referencing_entity_1->label());
|
||||
$this->assertEquals($expected_occurrences, $actual_occurrences);
|
||||
|
||||
// Repeat the process with another entity in order to check that the
|
||||
// 'recursive_render_id' counter is generated properly.
|
||||
$referencing_entity_2 = $storage->create(['name' => $this->randomMachineName()]);
|
||||
$referencing_entity_2->save();
|
||||
$referencing_entity_2->{$this->fieldName}->entity = $referencing_entity_2;
|
||||
$referencing_entity_2->save();
|
||||
|
||||
$build = $view_builder->view($referencing_entity_2, 'default');
|
||||
$output = (string) $renderer->renderRoot($build);
|
||||
|
||||
$actual_occurrences = substr_count($output, $referencing_entity_2->label());
|
||||
$this->assertEquals($expected_occurrences, $actual_occurrences);
|
||||
|
||||
// Now render both entities at the same time and check again.
|
||||
$build = $view_builder->viewMultiple([$referencing_entity_1, $referencing_entity_2], 'default');
|
||||
$output = (string) $renderer->renderRoot($build);
|
||||
|
||||
$actual_occurrences = substr_count($output, $referencing_entity_1->label());
|
||||
$this->assertEquals($expected_occurrences, $actual_occurrences);
|
||||
|
||||
$actual_occurrences = substr_count($output, $referencing_entity_2->label());
|
||||
$this->assertEquals($expected_occurrences, $actual_occurrences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the same entity referenced from different places.
|
||||
*/
|
||||
public function testEntityReferenceRecursiveProtectionWithManyRenderedEntities(): void {
|
||||
$formatter = 'entity_reference_entity_view';
|
||||
$view_builder = $this->entityTypeManager->getViewBuilder($this->entityType);
|
||||
|
||||
// Set the default view mode to use the 'entity_reference_entity_view'
|
||||
// formatter.
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => $formatter,
|
||||
])
|
||||
->save();
|
||||
|
||||
$storage = $this->entityTypeManager->getStorage($this->entityType);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $referenced_entity */
|
||||
$referenced_entity = $storage->create(['name' => $this->randomMachineName()]);
|
||||
|
||||
$range = range(0, 30);
|
||||
$referencing_entities = array_map(function () use ($storage, $referenced_entity) {
|
||||
$referencing_entity = $storage->create([
|
||||
'name' => $this->randomMachineName(),
|
||||
$this->fieldName => $referenced_entity,
|
||||
]);
|
||||
$referencing_entity->save();
|
||||
return $referencing_entity;
|
||||
}, $range);
|
||||
|
||||
$build = $view_builder->viewMultiple($referencing_entities, 'default');
|
||||
$output = $this->render($build);
|
||||
|
||||
// The title of entity_test entities is printed twice by default, so we have
|
||||
// to multiply the formatter's recursive rendering protection limit by 2.
|
||||
// Additionally, we have to take into account 2 additional occurrences of
|
||||
// the entity title because we're rendering the full entity, not just the
|
||||
// reference field.
|
||||
$expected_occurrences = 30 * 2 + 2;
|
||||
$actual_occurrences = substr_count($output, $referenced_entity->get('name')->value);
|
||||
$this->assertEquals($expected_occurrences, $actual_occurrences);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the label formatter.
|
||||
*/
|
||||
public function testLabelFormatter(): void {
|
||||
$this->installEntitySchema('entity_test_label');
|
||||
/** @var \Drupal\Core\Render\RendererInterface $renderer */
|
||||
$renderer = $this->container->get('renderer');
|
||||
$formatter = 'entity_reference_label';
|
||||
|
||||
// We need to create an anonymous user for access checks in the formatter.
|
||||
$this->createUser(values: [
|
||||
'uid' => 0,
|
||||
'status' => 0,
|
||||
'name' => '',
|
||||
]);
|
||||
|
||||
// The 'link' settings is TRUE by default.
|
||||
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter);
|
||||
|
||||
$expected_field_cacheability = [
|
||||
'contexts' => [],
|
||||
'tags' => [],
|
||||
'max-age' => Cache::PERMANENT,
|
||||
];
|
||||
$this->assertEquals($expected_field_cacheability, $build['#cache'], 'The field render array contains the entity access cacheability metadata');
|
||||
$expected_item_1 = [
|
||||
'#type' => 'link',
|
||||
'#title' => $this->referencedEntity->label(),
|
||||
'#url' => $this->referencedEntity->toUrl(),
|
||||
'#options' => $this->referencedEntity->toUrl()->getOptions(),
|
||||
'#cache' => [
|
||||
'contexts' => [
|
||||
'user.permissions',
|
||||
],
|
||||
'tags' => $this->referencedEntity->getCacheTags(),
|
||||
],
|
||||
];
|
||||
$this->assertEquals($renderer->renderRoot($expected_item_1), $renderer->renderRoot($build[0]), sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
|
||||
$this->assertEquals(CacheableMetadata::createFromRenderArray($expected_item_1), CacheableMetadata::createFromRenderArray($build[0]));
|
||||
|
||||
// The second referenced entity is "autocreated", therefore not saved and
|
||||
// lacking any URL info.
|
||||
$expected_item_2 = [
|
||||
'#plain_text' => $this->unsavedReferencedEntity->label(),
|
||||
'#entity' => $this->unsavedReferencedEntity,
|
||||
'#cache' => [
|
||||
'contexts' => [
|
||||
'user.permissions',
|
||||
],
|
||||
'tags' => $this->unsavedReferencedEntity->getCacheTags(),
|
||||
'max-age' => Cache::PERMANENT,
|
||||
],
|
||||
];
|
||||
$this->assertEquals($expected_item_2, $build[1], sprintf('The render array returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
|
||||
// Test with the 'link' setting set to FALSE.
|
||||
$build = $this->buildRenderArray([$this->referencedEntity, $this->unsavedReferencedEntity], $formatter, ['link' => FALSE]);
|
||||
$this->assertEquals($this->referencedEntity->label(), $build[0]['#plain_text'], sprintf('The markup returned by the %s formatter is correct for an item with a saved entity.', $formatter));
|
||||
$this->assertEquals($this->unsavedReferencedEntity->label(), $build[1]['#plain_text'], sprintf('The markup returned by the %s formatter is correct for an item with a unsaved entity.', $formatter));
|
||||
|
||||
// Test an entity type that doesn't have any link templates.
|
||||
$field_storage_config = FieldStorageConfig::loadByName($this->entityType, $this->fieldName);
|
||||
$field_storage_config->setSetting('target_type', 'entity_test_label');
|
||||
$field_storage_config->save();
|
||||
|
||||
$referenced_entity_with_no_link_template = EntityTestLabel::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
]);
|
||||
$referenced_entity_with_no_link_template->save();
|
||||
|
||||
$build = $this->buildRenderArray([$referenced_entity_with_no_link_template], $formatter, ['link' => TRUE]);
|
||||
$this->assertEquals($referenced_entity_with_no_link_template->label(), $build[0]['#plain_text'], sprintf('The markup returned by the %s formatter is correct for an entity type with no valid link template.', $formatter));
|
||||
|
||||
// Test link visibility if the URL is not accessible.
|
||||
$entity_with_user = EntityTest::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'user_id' => $this->createUser(),
|
||||
]);
|
||||
$entity_with_user->save();
|
||||
$build = $entity_with_user->get('user_id')->view(['type' => $formatter, 'settings' => ['link' => TRUE]]);
|
||||
$this->assertEquals($build[0]['#plain_text'], $entity_with_user->get('user_id')->entity->label(), 'For inaccessible links, the label should be displayed in plain text.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests formatters set the correct _referringItem on referenced entities.
|
||||
*/
|
||||
public function testFormatterReferencingItem(): void {
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityType);
|
||||
// Create a referencing entity and confirm that the _referringItem property
|
||||
// on the referenced entity in the built render array's items is set to the
|
||||
// field item on the referencing entity.
|
||||
$referencing_entity_1 = $storage->create([
|
||||
'name' => $this->randomMachineName(),
|
||||
$this->fieldName => $this->referencedEntity,
|
||||
]);
|
||||
$referencing_entity_1->save();
|
||||
$build_1 = $referencing_entity_1->get($this->fieldName)->view(['type' => 'entity_reference_entity_view']);
|
||||
$this->assertEquals($this->referencedEntity->id(), $build_1['#items'][0]->entity->id());
|
||||
$this->assertEquals($referencing_entity_1->id(), $build_1['#items'][0]->entity->_referringItem->getEntity()->id());
|
||||
$this->assertEquals($referencing_entity_1->id(), $build_1[0]['#' . $this->entityType]->_referringItem->getEntity()->id());
|
||||
// Create a second referencing entity and confirm that the _referringItem
|
||||
// property on the referenced entity in the built render array's items is
|
||||
// set to the field item on the second referencing entity.
|
||||
$referencing_entity_2 = $storage->create([
|
||||
'name' => $this->randomMachineName(),
|
||||
$this->fieldName => $this->referencedEntity,
|
||||
]);
|
||||
$referencing_entity_2->save();
|
||||
$build_2 = $referencing_entity_2->get($this->fieldName)->view(['type' => 'entity_reference_entity_view']);
|
||||
$this->assertEquals($this->referencedEntity->id(), $build_2['#items'][0]->entity->id());
|
||||
$this->assertEquals($referencing_entity_2->id(), $build_2['#items'][0]->entity->_referringItem->getEntity()->id());
|
||||
$this->assertEquals($referencing_entity_2->id(), $build_2[0]['#' . $this->entityType]->_referringItem->getEntity()->id());
|
||||
// Confirm that the _referringItem property for the entity referenced by the
|
||||
// first referencing entity is still set to the field item on the first
|
||||
// referencing entity.
|
||||
$this->assertEquals($referencing_entity_1->id(), $build_1['#items'][0]->entity->_referringItem->getEntity()->id());
|
||||
// Confirm that the _referringItem property is not the same for the two
|
||||
// render arrays.
|
||||
$this->assertNotEquals($build_1['#items'][0]->entity->_referringItem->getEntity()->id(), $build_2['#items'][0]->entity->_referringItem->getEntity()->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets field values and returns a render array.
|
||||
*
|
||||
* The render array structure is as built by
|
||||
* \Drupal\Core\Field\FieldItemListInterface::view().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface[] $referenced_entities
|
||||
* An array of entity objects that will be referenced.
|
||||
* @param string $formatter
|
||||
* The formatted plugin that will be used for building the render array.
|
||||
* @param array $formatter_options
|
||||
* Settings specific to the formatter. Defaults to the formatter's default
|
||||
* settings.
|
||||
*
|
||||
* @return array
|
||||
* A render array.
|
||||
*/
|
||||
protected function buildRenderArray(array $referenced_entities, $formatter, $formatter_options = []) {
|
||||
// Create the entity that will have the entity reference field.
|
||||
$referencing_entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityType)
|
||||
->create(['name' => $this->randomMachineName()]);
|
||||
|
||||
$items = $referencing_entity->get($this->fieldName);
|
||||
|
||||
// Assign the referenced entities.
|
||||
foreach ($referenced_entities as $referenced_entity) {
|
||||
$items[] = ['entity' => $referenced_entity];
|
||||
}
|
||||
|
||||
// Build the renderable array for the field.
|
||||
return $items->view(['type' => $formatter, 'settings' => $formatter_options]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,609 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\EntityReference;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestStringId;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\node\NodeInterface;
|
||||
use Drupal\taxonomy\TermInterface;
|
||||
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\user\Entity\User;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the new entity API for the entity reference field type.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class EntityReferenceItemTest extends FieldKernelTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'comment',
|
||||
'file',
|
||||
'taxonomy',
|
||||
'text',
|
||||
'filter',
|
||||
'views',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* The taxonomy vocabulary to test with.
|
||||
*
|
||||
* @var \Drupal\taxonomy\VocabularyInterface
|
||||
*/
|
||||
protected $vocabulary;
|
||||
|
||||
/**
|
||||
* The taxonomy term to test with.
|
||||
*
|
||||
* @var \Drupal\taxonomy\TermInterface
|
||||
*/
|
||||
protected $term;
|
||||
|
||||
/**
|
||||
* The test entity with a string ID.
|
||||
*
|
||||
* @var \Drupal\entity_test\Entity\EntityTestStringId
|
||||
*/
|
||||
protected $entityStringId;
|
||||
|
||||
/**
|
||||
* Sets up the test.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('entity_test_string_id');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('file');
|
||||
|
||||
$this->installSchema('comment', ['comment_entity_statistics']);
|
||||
$this->installSchema('node', ['node_access']);
|
||||
|
||||
$this->vocabulary = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->randomMachineName(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$this->vocabulary->save();
|
||||
|
||||
$this->term = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->vocabulary->id(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$this->term->save();
|
||||
|
||||
NodeType::create([
|
||||
'type' => $this->randomMachineName(),
|
||||
'name' => $this->randomString(),
|
||||
])->save();
|
||||
CommentType::create([
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
'target_entity_type_id' => 'node',
|
||||
])->save();
|
||||
|
||||
$this->entityStringId = EntityTestStringId::create([
|
||||
'id' => $this->randomMachineName(),
|
||||
]);
|
||||
$this->entityStringId->save();
|
||||
|
||||
// Use the util to create an instance.
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node', 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file');
|
||||
$this->createEntityReferenceField('entity_test_string_id', 'entity_test_string_id', 'field_test_entity_test', 'Test content entity reference with string ID', 'entity_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the entity reference field type for referencing content entities.
|
||||
*/
|
||||
public function testContentEntityReferenceItem(): void {
|
||||
$tid = $this->term->id();
|
||||
|
||||
// Just being able to create the entity like this verifies a lot of code.
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_taxonomy_term->target_id = $tid;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
$entity = EntityTest::load($entity->id());
|
||||
$this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test_taxonomy_term);
|
||||
$this->assertInstanceOf(FieldItemInterface::class, $entity->field_test_taxonomy_term[0]);
|
||||
$this->assertEquals($tid, $entity->field_test_taxonomy_term->target_id);
|
||||
$this->assertEquals($this->term->getName(), $entity->field_test_taxonomy_term->entity->getName());
|
||||
$this->assertEquals($tid, $entity->field_test_taxonomy_term->entity->id());
|
||||
$this->assertEquals($this->term->uuid(), $entity->field_test_taxonomy_term->entity->uuid());
|
||||
// Verify that the label for the target ID property definition is correct.
|
||||
$label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
|
||||
$this->assertInstanceOf(TranslatableMarkup::class, $label);
|
||||
$this->assertEquals('Taxonomy term ID', $label->render());
|
||||
|
||||
// Change the name of the term via the reference.
|
||||
$new_name = $this->randomMachineName();
|
||||
$entity->field_test_taxonomy_term->entity->setName($new_name);
|
||||
$entity->field_test_taxonomy_term->entity->save();
|
||||
// Verify it is the correct name.
|
||||
$term = Term::load($tid);
|
||||
$this->assertEquals($new_name, $term->getName());
|
||||
|
||||
// Make sure the computed term reflects updates to the term id.
|
||||
$term2 = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->term->bundle(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$term2->save();
|
||||
|
||||
// Test all the possible ways of assigning a value.
|
||||
$entity->field_test_taxonomy_term->target_id = $term->id();
|
||||
$this->assertEquals($term->id(), $entity->field_test_taxonomy_term->entity->id());
|
||||
$this->assertEquals($term->getName(), $entity->field_test_taxonomy_term->entity->getName());
|
||||
|
||||
$entity->field_test_taxonomy_term = [['target_id' => $term2->id()]];
|
||||
$this->assertEquals($term2->id(), $entity->field_test_taxonomy_term->entity->id());
|
||||
$this->assertEquals($term2->getName(), $entity->field_test_taxonomy_term->entity->getName());
|
||||
|
||||
// Test value assignment via the computed 'entity' property.
|
||||
$entity->field_test_taxonomy_term->entity = $term;
|
||||
$this->assertEquals($term->id(), $entity->field_test_taxonomy_term->target_id);
|
||||
$this->assertEquals($term->getName(), $entity->field_test_taxonomy_term->entity->getName());
|
||||
|
||||
$entity->field_test_taxonomy_term = [['entity' => $term2]];
|
||||
$this->assertEquals($term2->id(), $entity->field_test_taxonomy_term->target_id);
|
||||
$this->assertEquals($term2->getName(), $entity->field_test_taxonomy_term->entity->getName());
|
||||
|
||||
// Test assigning an invalid item throws an exception.
|
||||
try {
|
||||
$entity->field_test_taxonomy_term = ['target_id' => 'invalid', 'entity' => $term2];
|
||||
$this->fail('Assigning an invalid item throws an exception.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(\InvalidArgumentException::class, $e);
|
||||
}
|
||||
|
||||
// Delete terms so we have nothing to reference and try again
|
||||
$term->delete();
|
||||
$term2->delete();
|
||||
$entity = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$entity->save();
|
||||
|
||||
// Test the generateSampleValue() method.
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_taxonomy_term->generateSampleItems();
|
||||
$entity->field_test_taxonomy_vocabulary->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
|
||||
// Tests that setting an integer target ID together with an entity object
|
||||
// succeeds and does not cause any exceptions. There is no assertion here,
|
||||
// as the assignment should not throw any exceptions and if it does the
|
||||
// test will fail.
|
||||
// @see \Drupal\Core\Field\Plugin\Field\FieldType\EntityReferenceItem::setValue().
|
||||
$user = User::create(['name' => $this->randomString()]);
|
||||
$user->save();
|
||||
$entity = EntityTest::create(['user_id' => ['target_id' => (int) $user->id(), 'entity' => $user]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ::generateSampleValue() method.
|
||||
*/
|
||||
public function testGenerateSampleValue(): void {
|
||||
$entity = EntityTest::create();
|
||||
|
||||
// Test while a term exists.
|
||||
$entity->field_test_taxonomy_term->generateSampleItems();
|
||||
$this->assertInstanceOf(TermInterface::class, $entity->field_test_taxonomy_term->entity);
|
||||
$this->entityValidateAndSave($entity);
|
||||
|
||||
// Delete the term and test again.
|
||||
$this->term->delete();
|
||||
$entity->field_test_taxonomy_term->generateSampleItems();
|
||||
$this->assertInstanceOf(TermInterface::class, $entity->field_test_taxonomy_term->entity);
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the ::generateSampleValue() method when it has a circular reference.
|
||||
*/
|
||||
public function testGenerateSampleValueCircularReference(): void {
|
||||
// Delete the existing entity.
|
||||
$this->entityStringId->delete();
|
||||
|
||||
$entity_storage = \Drupal::entityTypeManager()->getStorage('entity_test');
|
||||
$entity = $entity_storage->createWithSampleValues('entity_test');
|
||||
$this->assertInstanceOf(EntityTestStringId::class, $entity->field_test_entity_test_string_id->entity);
|
||||
$this->assertInstanceOf(EntityTest::class, $entity->field_test_entity_test_string_id->entity->field_test_entity_test->entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests referencing content entities with string IDs.
|
||||
*/
|
||||
public function testContentEntityReferenceItemWithStringId(): void {
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_entity_test_string_id->target_id = $this->entityStringId->id();
|
||||
$entity->save();
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test');
|
||||
$storage->resetCache();
|
||||
$this->assertEquals($this->entityStringId->id(), $storage->load($entity->id())->field_test_entity_test_string_id->target_id);
|
||||
// Verify that the label for the target ID property definition is correct.
|
||||
$label = $entity->field_test_taxonomy_term->getFieldDefinition()->getFieldStorageDefinition()->getPropertyDefinition('target_id')->getLabel();
|
||||
$this->assertInstanceOf(TranslatableMarkup::class, $label);
|
||||
$this->assertEquals('Taxonomy term ID', $label->render());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the entity reference field type for referencing config entities.
|
||||
*/
|
||||
public function testConfigEntityReferenceItem(): void {
|
||||
$referenced_entity_id = $this->vocabulary->id();
|
||||
|
||||
// Just being able to create the entity like this verifies a lot of code.
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_taxonomy_vocabulary->target_id = $referenced_entity_id;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
$entity = EntityTest::load($entity->id());
|
||||
$this->assertInstanceOf(FieldItemListInterface::class, $entity->field_test_taxonomy_vocabulary);
|
||||
$this->assertInstanceOf(FieldItemInterface::class, $entity->field_test_taxonomy_vocabulary[0]);
|
||||
$this->assertEquals($referenced_entity_id, $entity->field_test_taxonomy_vocabulary->target_id);
|
||||
$this->assertEquals($this->vocabulary->label(), $entity->field_test_taxonomy_vocabulary->entity->label());
|
||||
$this->assertEquals($referenced_entity_id, $entity->field_test_taxonomy_vocabulary->entity->id());
|
||||
$this->assertEquals($this->vocabulary->uuid(), $entity->field_test_taxonomy_vocabulary->entity->uuid());
|
||||
|
||||
// Change the name of the term via the reference.
|
||||
$new_name = $this->randomMachineName();
|
||||
$entity->field_test_taxonomy_vocabulary->entity->set('name', $new_name);
|
||||
$entity->field_test_taxonomy_vocabulary->entity->save();
|
||||
// Verify it is the correct name.
|
||||
$vocabulary = Vocabulary::load($referenced_entity_id);
|
||||
$this->assertEquals($new_name, $vocabulary->label());
|
||||
|
||||
// Make sure the computed term reflects updates to the term id.
|
||||
$vocabulary2 = $vocabulary = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->randomMachineName(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$vocabulary2->save();
|
||||
|
||||
$entity->field_test_taxonomy_vocabulary->target_id = $vocabulary2->id();
|
||||
$this->assertEquals($vocabulary2->id(), $entity->field_test_taxonomy_vocabulary->entity->id());
|
||||
$this->assertEquals($vocabulary2->label(), $entity->field_test_taxonomy_vocabulary->entity->label());
|
||||
|
||||
// Delete terms so we have nothing to reference and try again
|
||||
$this->vocabulary->delete();
|
||||
$vocabulary2->delete();
|
||||
$entity = EntityTest::create(['name' => $this->randomMachineName()]);
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity auto create.
|
||||
*/
|
||||
public function testEntityAutoCreate(): void {
|
||||
// The term entity is unsaved here.
|
||||
$term = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->term->bundle(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$entity = EntityTest::create();
|
||||
// Now assign the unsaved term to the field.
|
||||
$entity->field_test_taxonomy_term->entity = $term;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
// This is equal to storing an entity to tempstore or cache and retrieving
|
||||
// it back. An example for this is node preview.
|
||||
$entity = serialize($entity);
|
||||
$entity = unserialize($entity);
|
||||
// And then the entity.
|
||||
$entity->save();
|
||||
$term = \Drupal::service('entity.repository')->loadEntityByUuid($term->getEntityTypeId(), $term->uuid());
|
||||
$this->assertEquals($entity->field_test_taxonomy_term->entity->id(), $term->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests saving order sequence doesn't matter.
|
||||
*/
|
||||
public function testEntitySaveOrder(): void {
|
||||
// The term entity is unsaved here.
|
||||
$term = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->term->bundle(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$entity = EntityTest::create();
|
||||
// Now assign the unsaved term to the field.
|
||||
$entity->field_test_taxonomy_term->entity = $term;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
// Now get the field value.
|
||||
$value = $entity->get('field_test_taxonomy_term');
|
||||
$this->assertArrayNotHasKey('target_id', $value);
|
||||
$this->assertNull($entity->field_test_taxonomy_term->target_id);
|
||||
// And then set it.
|
||||
$entity->field_test_taxonomy_term = $value;
|
||||
// Now save the term.
|
||||
$term->save();
|
||||
// And then the entity.
|
||||
$entity->save();
|
||||
$this->assertEquals($term->id(), $entity->field_test_taxonomy_term->entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the 'handler' field setting stores the proper plugin ID.
|
||||
*/
|
||||
public function testSelectionHandlerSettings(): void {
|
||||
$field_name = $this->randomMachineName();
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'entity_reference',
|
||||
'settings' => [
|
||||
'target_type' => 'entity_test',
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
// Do not specify any value for the 'handler' setting in order to verify
|
||||
// that the default handler with the correct derivative is used.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertEquals('default:entity_test', $field->getSetting('handler'));
|
||||
|
||||
// Change the target_type in the field storage, and check that the handler
|
||||
// was correctly reassigned in the field.
|
||||
$field_storage->setSetting('target_type', 'entity_test_rev');
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertEquals('default:entity_test_rev', $field->getSetting('handler'));
|
||||
|
||||
// Change the handler to another, non-derivative plugin.
|
||||
$field->setSetting('handler', 'views');
|
||||
$field->save();
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertEquals('views', $field->getSetting('handler'));
|
||||
|
||||
// Change the target_type in the field storage again, and check that the
|
||||
// non-derivative handler was unchanged.
|
||||
$field_storage->setSetting('target_type', 'entity_test_rev');
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertEquals('views', $field->getSetting('handler'));
|
||||
|
||||
// Check that selection handlers aren't changed during sync.
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => [
|
||||
'handler' => 'fake:thing',
|
||||
],
|
||||
'isSyncing' => TRUE,
|
||||
]);
|
||||
$this->assertEquals('fake:thing', $field->getSetting('handler'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests ValidReferenceConstraint with newly created and unsaved entities.
|
||||
*/
|
||||
public function testAutocreateValidation(): void {
|
||||
// The term entity is unsaved here.
|
||||
$term = Term::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'vid' => $this->term->bundle(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$entity = EntityTest::create([
|
||||
'field_test_taxonomy_term' => [
|
||||
'entity' => $term,
|
||||
'target_id' => NULL,
|
||||
],
|
||||
]);
|
||||
$errors = $entity->validate();
|
||||
// Using target_id of NULL is valid with an unsaved entity.
|
||||
$this->assertCount(0, $errors);
|
||||
// Using target_id of NULL is not valid with a saved entity.
|
||||
$term->save();
|
||||
$entity = EntityTest::create([
|
||||
'field_test_taxonomy_term' => [
|
||||
'entity' => $term,
|
||||
'target_id' => NULL,
|
||||
],
|
||||
]);
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This value should not be null.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_taxonomy_term.0', $errors[0]->getPropertyPath());
|
||||
// This should rectify the issue, favoring the entity over the target_id.
|
||||
$entity->save();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// Test with an unpublished and unsaved node.
|
||||
$title = $this->randomString();
|
||||
$node = Node::create([
|
||||
'title' => $title,
|
||||
'type' => 'node',
|
||||
'status' => NodeInterface::NOT_PUBLISHED,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_node' => [
|
||||
'entity' => $node,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This entity (node: ' . Html::escape($title) . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_node.0.entity', $errors[0]->getPropertyPath());
|
||||
|
||||
// Publish the node and try again.
|
||||
$node->setPublished();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// Test with a mix of valid and invalid nodes.
|
||||
$unsaved_unpublished_node_title = $this->randomString();
|
||||
$unsaved_unpublished_node = Node::create([
|
||||
'title' => $unsaved_unpublished_node_title,
|
||||
'type' => 'node',
|
||||
'status' => NodeInterface::NOT_PUBLISHED,
|
||||
]);
|
||||
|
||||
$saved_unpublished_node_title = $this->randomString();
|
||||
$saved_unpublished_node = Node::create([
|
||||
'title' => $saved_unpublished_node_title,
|
||||
'type' => 'node',
|
||||
'status' => NodeInterface::NOT_PUBLISHED,
|
||||
]);
|
||||
$saved_unpublished_node->save();
|
||||
|
||||
$saved_published_node_title = $this->randomString();
|
||||
$saved_published_node = Node::create([
|
||||
'title' => $saved_published_node_title,
|
||||
'type' => 'node',
|
||||
'status' => NodeInterface::PUBLISHED,
|
||||
]);
|
||||
$saved_published_node->save();
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_node' => [
|
||||
[
|
||||
'entity' => $unsaved_unpublished_node,
|
||||
],
|
||||
[
|
||||
'target_id' => $saved_unpublished_node->id(),
|
||||
],
|
||||
[
|
||||
'target_id' => $saved_published_node->id(),
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(2, $errors);
|
||||
$this->assertEquals('This entity (node: ' . Html::escape($unsaved_unpublished_node_title) . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_node.0.entity', $errors[0]->getPropertyPath());
|
||||
$this->assertEquals('This entity (node: ' . Html::escape($saved_unpublished_node->id()) . ') cannot be referenced.', $errors[1]->getMessage());
|
||||
$this->assertEquals('field_test_node.1.target_id', $errors[1]->getPropertyPath());
|
||||
|
||||
// Publish one of the nodes and try again.
|
||||
$saved_unpublished_node->setPublished();
|
||||
$saved_unpublished_node->save();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This entity (node: ' . Html::escape($unsaved_unpublished_node_title) . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_node.0.entity', $errors[0]->getPropertyPath());
|
||||
|
||||
// Publish the last invalid node and try again.
|
||||
$unsaved_unpublished_node->setPublished();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// Test with an unpublished and unsaved comment.
|
||||
$title = $this->randomString();
|
||||
$comment = Comment::create([
|
||||
'subject' => $title,
|
||||
'comment_type' => 'comment',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_comment' => [
|
||||
'entity' => $comment,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This entity (comment: ' . Html::escape($title) . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_comment.0.entity', $errors[0]->getPropertyPath());
|
||||
|
||||
// Publish the comment and try again.
|
||||
$comment->setPublished();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// Test with an inactive and unsaved user.
|
||||
$name = $this->randomString();
|
||||
$user = User::create([
|
||||
'name' => $name,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_user' => [
|
||||
'entity' => $user,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This entity (user: ' . Html::escape($name) . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_user.0.entity', $errors[0]->getPropertyPath());
|
||||
|
||||
// Activate the user and try again.
|
||||
$user->activate();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
|
||||
// Test with a temporary and unsaved file.
|
||||
$filename = $this->randomMachineName() . '.txt';
|
||||
$file = File::create([
|
||||
'filename' => $filename,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_file' => [
|
||||
'entity' => $file,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(1, $errors);
|
||||
$this->assertEquals('This entity (file: ' . $filename . ') cannot be referenced.', $errors[0]->getMessage());
|
||||
$this->assertEquals('field_test_file.0.entity', $errors[0]->getPropertyPath());
|
||||
|
||||
// Set the file as permanent and try again.
|
||||
$file->setPermanent();
|
||||
$errors = $entity->validate();
|
||||
$this->assertCount(0, $errors);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\EntityReference;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Logger\RfcLogLevel;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Symfony\Component\ErrorHandler\BufferingLogger;
|
||||
|
||||
/**
|
||||
* Tests entity reference field settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class EntityReferenceSettingsTest extends KernelTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'taxonomy',
|
||||
'field',
|
||||
'user',
|
||||
'text',
|
||||
'entity_test',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* Testing node type.
|
||||
*
|
||||
* @var \Drupal\node\Entity\NodeType
|
||||
*/
|
||||
protected $nodeType;
|
||||
|
||||
/**
|
||||
* Testing vocabulary.
|
||||
*
|
||||
* @var \Drupal\taxonomy\Entity\Vocabulary
|
||||
*/
|
||||
protected $vocabulary;
|
||||
|
||||
/**
|
||||
* An entity bundle that is not stored as a configuration entity.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $customBundle;
|
||||
|
||||
/**
|
||||
* The service name for a logger implementation that collects anything logged.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $testLogServiceName = 'entity_reference_settings_test.logger';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('entity_test');
|
||||
|
||||
$this->nodeType = NodeType::create([
|
||||
'type' => $this->randomMachineName(),
|
||||
'name' => $this->randomString(),
|
||||
]);
|
||||
$this->nodeType->save();
|
||||
|
||||
// Create a custom bundle.
|
||||
$this->customBundle = 'test_bundle_' . $this->randomMachineName();
|
||||
EntityTestHelper::createBundle($this->customBundle, NULL, 'entity_test');
|
||||
|
||||
// Prepare the logger for collecting the expected critical error.
|
||||
$this->container->get($this->testLogServiceName)->cleanLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that config bundle deletions are mirrored in field config settings.
|
||||
*/
|
||||
public function testConfigTargetBundleDeletion(): void {
|
||||
// Create two vocabularies.
|
||||
/** @var \Drupal\taxonomy\Entity\Vocabulary[] $vocabularies */
|
||||
$vocabularies = [];
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$vid = $this->randomMachineName();
|
||||
$vocabularies[$i] = Vocabulary::create([
|
||||
'name' => $this->randomString(),
|
||||
'vid' => $vid,
|
||||
]);
|
||||
$vocabularies[$i]->save();
|
||||
}
|
||||
// Attach an entity reference field to $this->nodeType.
|
||||
$name = $this->randomMachineName();
|
||||
$label = $this->randomString();
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$vocabularies[0]->id() => $vocabularies[0]->id(),
|
||||
$vocabularies[1]->id() => $vocabularies[1]->id(),
|
||||
],
|
||||
];
|
||||
$this->createEntityReferenceField('node', $this->nodeType->id(), $name, $label, 'taxonomy_term', 'default', $handler_settings);
|
||||
|
||||
// Check that the 'target_bundle' setting contains the vocabulary.
|
||||
$field_config = FieldConfig::loadByName('node', $this->nodeType->id(), $name);
|
||||
$actual_handler_settings = $field_config->getSetting('handler_settings');
|
||||
$this->assertEquals($handler_settings, $actual_handler_settings);
|
||||
|
||||
// Delete the vocabulary.
|
||||
$vocabularies[0]->delete();
|
||||
// Ensure that noting is logged.
|
||||
$this->assertEmpty($this->container->get($this->testLogServiceName)->cleanLogs());
|
||||
|
||||
// Check that the deleted vocabulary is no longer present in the
|
||||
// 'target_bundles' field setting.
|
||||
$field_config = FieldConfig::loadByName('node', $this->nodeType->id(), $name);
|
||||
$handler_settings = $field_config->getSetting('handler_settings');
|
||||
$this->assertEquals([$vocabularies[1]->id() => $vocabularies[1]->id()], $handler_settings['target_bundles']);
|
||||
|
||||
// Delete the other vocabulary.
|
||||
$vocabularies[1]->delete();
|
||||
// Ensure that field_field_config_presave() logs the expected critical
|
||||
// error.
|
||||
$log_message = $this->container->get($this->testLogServiceName)->cleanLogs()[0];
|
||||
$this->assertEquals(RfcLogLevel::CRITICAL, $log_message[0]);
|
||||
$this->assertEquals('The %field_name entity reference field (entity_type: %entity_type, bundle: %bundle) no longer has any valid bundle it can reference. The field is not working correctly anymore and has to be adjusted.', $log_message[1]);
|
||||
$this->assertEquals($field_config->getName(), $log_message[2]['%field_name']);
|
||||
$this->assertEquals('node', $log_message[2]['%entity_type']);
|
||||
$this->assertEquals($this->nodeType->id(), $log_message[2]['%bundle']);
|
||||
|
||||
// Check that the deleted bundle is no longer present in the
|
||||
// 'target_bundles' field setting.
|
||||
$field_config = FieldConfig::loadByName('node', $this->nodeType->id(), $name);
|
||||
$handler_settings = $field_config->getSetting('handler_settings');
|
||||
$this->assertEquals([], $handler_settings['target_bundles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that deletions of custom bundles are mirrored in field settings.
|
||||
*/
|
||||
public function testCustomTargetBundleDeletion(): void {
|
||||
// Attach an entity reference field to $this->nodeType.
|
||||
$name = $this->randomMachineName();
|
||||
$label = $this->randomString();
|
||||
$handler_settings = ['target_bundles' => [$this->customBundle => $this->customBundle]];
|
||||
$this->createEntityReferenceField('node', $this->nodeType->id(), $name, $label, 'entity_test', 'default', $handler_settings);
|
||||
|
||||
// Check that the 'target_bundle' setting contains the custom bundle.
|
||||
$field_config = FieldConfig::loadByName('node', $this->nodeType->id(), $name);
|
||||
$actual_handler_settings = $field_config->getSetting('handler_settings');
|
||||
$this->assertEquals($handler_settings, $actual_handler_settings);
|
||||
|
||||
// Delete the custom bundle.
|
||||
EntityTestHelper::deleteBundle($this->customBundle, 'entity_test');
|
||||
|
||||
// Ensure that field_field_config_presave() logs the expected critical
|
||||
// error.
|
||||
$log_message = $this->container->get($this->testLogServiceName)->cleanLogs()[0];
|
||||
$this->assertEquals(RfcLogLevel::CRITICAL, $log_message[0]);
|
||||
$this->assertEquals('The %field_name entity reference field (entity_type: %entity_type, bundle: %bundle) no longer has any valid bundle it can reference. The field is not working correctly anymore and has to be adjusted.', $log_message[1]);
|
||||
$this->assertEquals($field_config->getName(), $log_message[2]['%field_name']);
|
||||
$this->assertEquals('node', $log_message[2]['%entity_type']);
|
||||
$this->assertEquals($this->nodeType->id(), $log_message[2]['%bundle']);
|
||||
|
||||
// Check that the deleted bundle is no longer present in the
|
||||
// 'target_bundles' field setting.
|
||||
$field_config = FieldConfig::loadByName('node', $this->nodeType->id(), $name);
|
||||
$handler_settings = $field_config->getSetting('handler_settings');
|
||||
$this->assertEmpty($handler_settings['target_bundles']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function register(ContainerBuilder $container): void {
|
||||
parent::register($container);
|
||||
$container
|
||||
->register($this->testLogServiceName, BufferingLogger::class)
|
||||
->addTag('logger');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,367 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\EntityReference\Views;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestMulChanged;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\Entity\EntityTestMul;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests entity reference relationship data.
|
||||
*
|
||||
* @group entity_reference
|
||||
*
|
||||
* @see \Drupal\views\Hook\ViewsViewsHooks::fieldViewsData()
|
||||
*/
|
||||
class EntityReferenceRelationshipTest extends ViewsKernelTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
|
||||
/**
|
||||
* Views used by this test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $testViews = [
|
||||
'test_entity_reference_entity_test_view',
|
||||
'test_entity_reference_entity_test_view_long',
|
||||
'test_entity_reference_reverse_entity_test_view',
|
||||
'test_entity_reference_entity_test_mul_view',
|
||||
'test_entity_reference_reverse_entity_test_mul_view',
|
||||
'test_entity_reference_group_by_empty_relationships',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'field',
|
||||
'entity_test',
|
||||
'views',
|
||||
'entity_reference_test_views',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity_test entities used by the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $entities = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp($import_test_views = TRUE): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('user_role');
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('entity_test_mul');
|
||||
$this->installEntitySchema('entity_test_mul_changed');
|
||||
|
||||
// Create reference from entity_test to entity_test_mul.
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_data', 'field_test_data', 'entity_test_mul');
|
||||
|
||||
// Create reference from entity_test_mul to entity_test.
|
||||
$this->createEntityReferenceField('entity_test_mul', 'entity_test_mul', 'field_data_test', 'field_data_test', 'entity_test');
|
||||
|
||||
// Create another field for testing with a long name. So its storage name
|
||||
// will become hashed. Use entity_test_mul_changed, so the resulting field
|
||||
// tables created will be greater than 48 chars long.
|
||||
// @see \Drupal\Core\Entity\Sql\DefaultTableMapping::generateFieldTableName()
|
||||
$this->createEntityReferenceField('entity_test_mul_changed', 'entity_test_mul_changed', 'field_test_data_with_a_long_name', 'field_test_data_with_a_long_name', 'entity_test');
|
||||
|
||||
// Create reference from entity_test_mul to entity_test cardinality:
|
||||
// infinite.
|
||||
$this->createEntityReferenceField('entity_test_mul', 'entity_test_mul', 'field_data_test_unlimited', 'field_data_test_unlimited', 'entity_test', 'default', [], FieldStorageConfig::CARDINALITY_UNLIMITED);
|
||||
|
||||
ViewTestData::createTestViews(static::class, ['entity_reference_test_views']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests using the views relationship.
|
||||
*/
|
||||
public function testNoDataTableRelationship(): void {
|
||||
|
||||
// Create some test entities which link each other.
|
||||
$referenced_entity = EntityTestMul::create();
|
||||
$referenced_entity->save();
|
||||
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_data->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->assertEquals($referenced_entity->id(), $entity->field_test_data[0]->entity->id());
|
||||
$this->entities[] = $entity;
|
||||
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_test_data->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->assertEquals($referenced_entity->id(), $entity->field_test_data[0]->entity->id());
|
||||
$this->entities[] = $entity;
|
||||
|
||||
Views::viewsData()->clear();
|
||||
|
||||
// Check the generated views data.
|
||||
$views_data = Views::viewsData()->get('entity_test__field_test_data');
|
||||
$this->assertEquals('standard', $views_data['field_test_data']['relationship']['id']);
|
||||
$this->assertEquals('entity_test_mul_property_data', $views_data['field_test_data']['relationship']['base']);
|
||||
$this->assertEquals('id', $views_data['field_test_data']['relationship']['base field']);
|
||||
$this->assertEquals('field_test_data_target_id', $views_data['field_test_data']['relationship']['relationship field']);
|
||||
$this->assertEquals('entity_test_mul', $views_data['field_test_data']['relationship']['entity type']);
|
||||
|
||||
// Check the backwards reference.
|
||||
$views_data = Views::viewsData()->get('entity_test_mul_property_data');
|
||||
$this->assertEquals('entity_reverse', $views_data['reverse__entity_test__field_test_data']['relationship']['id']);
|
||||
$this->assertEquals('entity_test', $views_data['reverse__entity_test__field_test_data']['relationship']['base']);
|
||||
$this->assertEquals('id', $views_data['reverse__entity_test__field_test_data']['relationship']['base field']);
|
||||
$this->assertEquals('entity_test__field_test_data', $views_data['reverse__entity_test__field_test_data']['relationship']['field table']);
|
||||
$this->assertEquals('field_test_data_target_id', $views_data['reverse__entity_test__field_test_data']['relationship']['field field']);
|
||||
$this->assertEquals('field_test_data', $views_data['reverse__entity_test__field_test_data']['relationship']['field_name']);
|
||||
$this->assertEquals('entity_test', $views_data['reverse__entity_test__field_test_data']['relationship']['entity_type']);
|
||||
$this->assertEquals(['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], $views_data['reverse__entity_test__field_test_data']['relationship']['join_extra'][0]);
|
||||
|
||||
// Check an actual test view.
|
||||
$view = Views::getView('test_entity_reference_entity_test_view');
|
||||
$this->executeView($view);
|
||||
/** @var \Drupal\views\ResultRow $row */
|
||||
foreach ($view->result as $index => $row) {
|
||||
// Check that the actual ID of the entity is the expected one.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->id);
|
||||
|
||||
// Also check that we have the correct result entity.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->_entity->id());
|
||||
|
||||
// Test the forward relationship.
|
||||
$this->assertEquals(1, $row->entity_test_mul_property_data_entity_test__field_test_data_i);
|
||||
|
||||
// Test that the correct relationship entity is on the row.
|
||||
$this->assertEquals(1, $row->_relationship_entities['field_test_data']->id());
|
||||
$this->assertEquals('entity_test_mul', $row->_relationship_entities['field_test_data']->bundle());
|
||||
}
|
||||
|
||||
// Check the backwards reference view.
|
||||
$view = Views::getView('test_entity_reference_reverse_entity_test_view');
|
||||
$this->executeView($view);
|
||||
/** @var \Drupal\views\ResultRow $row */
|
||||
foreach ($view->result as $index => $row) {
|
||||
$this->assertEquals(1, $row->id);
|
||||
$this->assertEquals(1, $row->_entity->id());
|
||||
|
||||
// Test the backwards relationship.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->field_test_data_entity_test_mul_property_data_id);
|
||||
|
||||
// Test that the correct relationship entity is on the row.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->_relationship_entities['reverse__entity_test__field_test_data']->id());
|
||||
$this->assertEquals('entity_test', $row->_relationship_entities['reverse__entity_test__field_test_data']->bundle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests views data generated for relationship.
|
||||
*
|
||||
* @see entity_reference_field_views_data()
|
||||
*/
|
||||
public function testDataTableRelationship(): void {
|
||||
|
||||
// Create some test entities which link each other.
|
||||
$referenced_entity = EntityTest::create();
|
||||
$referenced_entity->save();
|
||||
|
||||
$entity = EntityTestMul::create();
|
||||
$entity->field_data_test->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->assertEquals($referenced_entity->id(), $entity->field_data_test[0]->entity->id());
|
||||
$this->entities[] = $entity;
|
||||
|
||||
$entity = EntityTestMul::create();
|
||||
$entity->field_data_test->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->assertEquals($referenced_entity->id(), $entity->field_data_test[0]->entity->id());
|
||||
$this->entities[] = $entity;
|
||||
|
||||
Views::viewsData()->clear();
|
||||
|
||||
// Check the generated views data.
|
||||
$views_data = Views::viewsData()->get('entity_test_mul__field_data_test');
|
||||
$this->assertEquals('standard', $views_data['field_data_test']['relationship']['id']);
|
||||
$this->assertEquals('entity_test', $views_data['field_data_test']['relationship']['base']);
|
||||
$this->assertEquals('id', $views_data['field_data_test']['relationship']['base field']);
|
||||
$this->assertEquals('field_data_test_target_id', $views_data['field_data_test']['relationship']['relationship field']);
|
||||
$this->assertEquals('entity_test', $views_data['field_data_test']['relationship']['entity type']);
|
||||
|
||||
// Check the backwards reference.
|
||||
$views_data = Views::viewsData()->get('entity_test');
|
||||
$this->assertEquals('entity_reverse', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['id']);
|
||||
$this->assertEquals('entity_test_mul_property_data', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['base']);
|
||||
$this->assertEquals('id', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['base field']);
|
||||
$this->assertEquals('entity_test_mul__field_data_test', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['field table']);
|
||||
$this->assertEquals('field_data_test_target_id', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['field field']);
|
||||
$this->assertEquals('field_data_test', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['field_name']);
|
||||
$this->assertEquals('entity_test_mul', $views_data['reverse__entity_test_mul__field_data_test']['relationship']['entity_type']);
|
||||
$this->assertEquals(['field' => 'deleted', 'value' => 0, 'numeric' => TRUE], $views_data['reverse__entity_test_mul__field_data_test']['relationship']['join_extra'][0]);
|
||||
|
||||
// Check an actual test view.
|
||||
$view = Views::getView('test_entity_reference_entity_test_mul_view');
|
||||
$this->executeView($view);
|
||||
/** @var \Drupal\views\ResultRow $row */
|
||||
foreach ($view->result as $index => $row) {
|
||||
// Check that the actual ID of the entity is the expected one.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->id);
|
||||
|
||||
// Also check that we have the correct result entity.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->_entity->id());
|
||||
|
||||
// Test the forward relationship.
|
||||
$this->assertEquals(1, $row->entity_test_entity_test_mul__field_data_test_id);
|
||||
|
||||
// Test that the correct relationship entity is on the row.
|
||||
$this->assertEquals(1, $row->_relationship_entities['field_data_test']->id());
|
||||
$this->assertEquals('entity_test', $row->_relationship_entities['field_data_test']->bundle());
|
||||
|
||||
}
|
||||
|
||||
// Check the backwards reference view.
|
||||
$view = Views::getView('test_entity_reference_reverse_entity_test_mul_view');
|
||||
$this->executeView($view);
|
||||
/** @var \Drupal\views\ResultRow $row */
|
||||
foreach ($view->result as $index => $row) {
|
||||
$this->assertEquals(1, $row->id);
|
||||
$this->assertEquals(1, $row->_entity->id());
|
||||
|
||||
// Test the backwards relationship.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->field_data_test_entity_test_id);
|
||||
|
||||
// Test that the correct relationship entity is on the row.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->_relationship_entities['reverse__entity_test_mul__field_data_test']->id());
|
||||
$this->assertEquals('entity_test_mul', $row->_relationship_entities['reverse__entity_test_mul__field_data_test']->bundle());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests views data generated for relationship.
|
||||
*
|
||||
* @see entity_reference_field_views_data()
|
||||
*/
|
||||
public function testDataTableRelationshipWithLongFieldName(): void {
|
||||
// Create some test entities which link each other.
|
||||
$referenced_entity = EntityTest::create();
|
||||
$referenced_entity->save();
|
||||
|
||||
$entity = EntityTestMulChanged::create();
|
||||
$entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->entities[] = $entity;
|
||||
|
||||
$entity = EntityTestMulChanged::create();
|
||||
$entity->field_test_data_with_a_long_name->target_id = $referenced_entity->id();
|
||||
$entity->save();
|
||||
$this->entities[] = $entity;
|
||||
|
||||
Views::viewsData()->clear();
|
||||
|
||||
// Check an actual test view.
|
||||
$view = Views::getView('test_entity_reference_entity_test_view_long');
|
||||
$this->executeView($view);
|
||||
/** @var \Drupal\views\ResultRow $row */
|
||||
foreach ($view->result as $index => $row) {
|
||||
// Check that the actual ID of the entity is the expected one.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->id);
|
||||
|
||||
// Also check that we have the correct result entity.
|
||||
$this->assertEquals($this->entities[$index]->id(), $row->_entity->id());
|
||||
|
||||
// Test the forward relationship.
|
||||
// $this->assertEquals(1, $row->entity_test_entity_test_mul__field_data_test_id);
|
||||
|
||||
// Test that the correct relationship entity is on the row.
|
||||
$this->assertEquals(1, $row->_relationship_entities['field_test_data_with_a_long_name']->id());
|
||||
$this->assertEquals('entity_test', $row->_relationship_entities['field_test_data_with_a_long_name']->bundle());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests group by with optional and empty relationship.
|
||||
*/
|
||||
public function testGroupByWithEmptyRelationships(): void {
|
||||
$entities = [];
|
||||
// Create 4 entities with name1 and 3 entities with name2.
|
||||
for ($i = 1; $i <= 4; $i++) {
|
||||
$entity = [
|
||||
'name' => 'name' . $i,
|
||||
];
|
||||
$entity = EntityTest::create($entity);
|
||||
$entities[] = $entity;
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
$entity = EntityTestMul::create([
|
||||
'name' => 'name1',
|
||||
]);
|
||||
$entity->field_data_test_unlimited = [
|
||||
['target_id' => $entities[0]->id()],
|
||||
['target_id' => $entities[1]->id()],
|
||||
['target_id' => $entities[2]->id()],
|
||||
];
|
||||
$entity->save();
|
||||
|
||||
$entity = EntityTestMul::create([
|
||||
'name' => 'name2',
|
||||
]);
|
||||
$entity->field_data_test_unlimited = [
|
||||
['target_id' => $entities[0]->id()],
|
||||
['target_id' => $entities[1]->id()],
|
||||
];
|
||||
$entity->save();
|
||||
|
||||
$entity = EntityTestMul::create([
|
||||
'name' => 'name3',
|
||||
]);
|
||||
$entity->field_data_test_unlimited->target_id = $entities[0]->id();
|
||||
$entity->save();
|
||||
|
||||
$view = Views::getView('test_entity_reference_group_by_empty_relationships');
|
||||
$this->executeView($view);
|
||||
$this->assertCount(4, $view->result);
|
||||
// First three results should contain a reference from EntityTestMul.
|
||||
$this->assertNotEmpty($view->getStyle()->getField(0, 'name_2'));
|
||||
$this->assertNotEmpty($view->getStyle()->getField(1, 'name_2'));
|
||||
$this->assertNotEmpty($view->getStyle()->getField(2, 'name_2'));
|
||||
// Fourth result has no reference from EntityTestMul hence the output for
|
||||
// should be empty.
|
||||
$this->assertEquals('', $view->getStyle()->getField(3, 'name_2'));
|
||||
|
||||
$fields = $view->field;
|
||||
// Check getValue for reference with a value. The first 3 rows reference
|
||||
// EntityTestMul, so have value 'name1'.
|
||||
$this->assertEquals('name1', $fields['name_2']->getValue($view->result[0]));
|
||||
$this->assertEquals('name1', $fields['name_2']->getValue($view->result[1]));
|
||||
$this->assertEquals('name1', $fields['name_2']->getValue($view->result[2]));
|
||||
// Ensure getValue works on empty references.
|
||||
$this->assertNull($fields['name_2']->getValue($view->result[3]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that config entities don't get relationships added.
|
||||
*/
|
||||
public function testEntityReferenceConfigEntity(): void {
|
||||
// Create reference from entity_test to a config entity.
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_config_entity', 'field_test_config_entity', 'user_role');
|
||||
Views::viewsData()->clear();
|
||||
$views_data = Views::viewsData()->getAll();
|
||||
// Test that a relationship got added for content entities but not config
|
||||
// entities.
|
||||
$this->assertTrue(isset($views_data['entity_test__field_test_data']['field_test_data']['relationship']));
|
||||
$this->assertFalse(isset($views_data['entity_test__field_test_config_entity']['field_test_config_entity']['relationship']));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,177 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\EntityReference\Views;
|
||||
|
||||
use Drupal\Component\Render\MarkupInterface;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\Tests\node\Traits\NodeCreationTrait;
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Tests entity reference selection handler.
|
||||
*
|
||||
* @group entity_reference
|
||||
*/
|
||||
class SelectionTest extends KernelTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
use NodeCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_reference_test',
|
||||
'entity_test',
|
||||
'field',
|
||||
'filter',
|
||||
'node',
|
||||
'system',
|
||||
'user',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* Nodes for testing.
|
||||
*
|
||||
* @var string[][]
|
||||
*/
|
||||
protected $nodes = [];
|
||||
|
||||
/**
|
||||
* The selection handler.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
|
||||
*/
|
||||
protected $selectionHandler;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['entity_reference_test', 'filter']);
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('entity_test');
|
||||
|
||||
// Create test nodes.
|
||||
$type = $this->randomMachineName();
|
||||
NodeType::create([
|
||||
'type' => $type,
|
||||
'name' => $this->randomString(),
|
||||
])->save();
|
||||
$node1 = $this->createNode(['type' => $type]);
|
||||
$node2 = $this->createNode(['type' => $type]);
|
||||
$node3 = $this->createNode();
|
||||
|
||||
foreach ([$node1, $node2, $node3] as $node) {
|
||||
$this->nodes[$node->id()] = $node;
|
||||
}
|
||||
|
||||
// Ensure the bundle to which the field is attached actually exists, or we
|
||||
// will get config validation errors.
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
// Create an entity reference field.
|
||||
$handler_settings = [
|
||||
'view' => [
|
||||
'view_name' => 'test_entity_reference',
|
||||
'display_name' => 'entity_reference_1',
|
||||
],
|
||||
];
|
||||
$this->createEntityReferenceField('entity_test', 'test_bundle', 'test_field', $this->randomString(), 'node', 'views', $handler_settings);
|
||||
$field_config = FieldConfig::loadByName('entity_test', 'test_bundle', 'test_field');
|
||||
$this->selectionHandler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the selection handler.
|
||||
*/
|
||||
public function testSelectionHandler(): void {
|
||||
// Tests the selection handler.
|
||||
$this->assertResults($this->selectionHandler->getReferenceableEntities());
|
||||
|
||||
// Add a relationship to the view.
|
||||
$view = Views::getView('test_entity_reference');
|
||||
$view->setDisplay();
|
||||
$view->displayHandlers->get('default')->setOption('relationships', [
|
||||
'test_relationship' => [
|
||||
'id' => 'uid',
|
||||
'table' => 'node_field_data',
|
||||
'field' => 'uid',
|
||||
],
|
||||
]);
|
||||
|
||||
// Add a filter depending on the relationship to the test view.
|
||||
$view->displayHandlers->get('default')->setOption('filters', [
|
||||
'uid' => [
|
||||
'id' => 'uid',
|
||||
'table' => 'users_field_data',
|
||||
'field' => 'uid',
|
||||
'relationship' => 'test_relationship',
|
||||
],
|
||||
]);
|
||||
|
||||
// Set view to distinct so only one row per node is returned.
|
||||
$query_options = $view->display_handler->getOption('query');
|
||||
$query_options['options']['distinct'] = TRUE;
|
||||
$view->display_handler->setOption('query', $query_options);
|
||||
$view->save();
|
||||
|
||||
// Tests the selection handler with a relationship.
|
||||
$this->assertResults($this->selectionHandler->getReferenceableEntities());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the anchor tag stripping.
|
||||
*
|
||||
* Unstripped results based on the data above will result in output like so:
|
||||
* ...<a href="/node/1" hreflang="en">Test first node</a>...
|
||||
* ...<a href="/node/2" hreflang="en">Test second node</a>...
|
||||
* ...<a href="/node/3" hreflang="en">Test third node</a>...
|
||||
* If we expect our output to not have the <a> tags, and this matches what's
|
||||
* produced by the tag-stripping method, we'll know that it's working.
|
||||
*/
|
||||
public function testAnchorTagStripping(): void {
|
||||
$filtered_rendered_results_formatted = [];
|
||||
foreach ($this->selectionHandler->getReferenceableEntities() as $subresults) {
|
||||
$filtered_rendered_results_formatted += array_map(fn(MarkupInterface $markup): string => (string) $markup, $subresults);
|
||||
}
|
||||
|
||||
// Note the missing <a> tags.
|
||||
$expected = [
|
||||
1 => '<span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[1]->label()) . '</span></span>',
|
||||
2 => '<span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[2]->label()) . '</span></span>',
|
||||
3 => '<span class="views-field views-field-title"><span class="field-content">' . Html::escape($this->nodes[3]->label()) . '</span></span>',
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $filtered_rendered_results_formatted, 'Anchor tag stripping has failed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm the expected results are returned.
|
||||
*
|
||||
* @param array $result
|
||||
* Query results keyed by node type and nid.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertResults(array $result): void {
|
||||
foreach ($result as $node_type => $values) {
|
||||
foreach ($values as $nid => $label) {
|
||||
$this->assertSame($node_type, $this->nodes[$nid]->bundle());
|
||||
$this->assertSame(trim(strip_tags((string) $label)), Html::escape($this->nodes[$nid]->label()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
395
web/core/modules/field/tests/src/Kernel/FieldAttachOtherTest.php
Normal file
395
web/core/modules/field/tests/src/Kernel/FieldAttachOtherTest.php
Normal file
@ -0,0 +1,395 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Form\FormState;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
|
||||
/**
|
||||
* Tests other Field API functions.
|
||||
*
|
||||
* @group field
|
||||
* @todo move this to the Entity module
|
||||
*/
|
||||
class FieldAttachOtherTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->createFieldWithStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rendering fields with EntityDisplay build().
|
||||
*/
|
||||
public function testEntityDisplayBuild(): void {
|
||||
$this->createFieldWithStorage('_2');
|
||||
|
||||
$entity_type = 'entity_test';
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create();
|
||||
|
||||
// Populate values to be displayed.
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
|
||||
$entity_init->{$this->fieldTestData->field_name}->setValue($values);
|
||||
$values_2 = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
|
||||
$entity_init->{$this->fieldTestData->field_name_2}->setValue($values_2);
|
||||
|
||||
// Simple formatter, label displayed.
|
||||
$entity = clone($entity_init);
|
||||
$display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($entity_type, $entity->bundle(), 'full');
|
||||
|
||||
$formatter_setting = $this->randomMachineName();
|
||||
$display_options = [
|
||||
'label' => 'above',
|
||||
'type' => 'field_test_default',
|
||||
'settings' => [
|
||||
'test_formatter_setting' => $formatter_setting,
|
||||
],
|
||||
];
|
||||
$display->setComponent($this->fieldTestData->field_name, $display_options);
|
||||
|
||||
$formatter_setting_2 = $this->randomMachineName();
|
||||
$display_options_2 = [
|
||||
'label' => 'above',
|
||||
'type' => 'field_test_default',
|
||||
'settings' => [
|
||||
'test_formatter_setting' => $formatter_setting_2,
|
||||
],
|
||||
];
|
||||
$display->setComponent($this->fieldTestData->field_name_2, $display_options_2);
|
||||
|
||||
// View all fields.
|
||||
$content = $display->build($entity);
|
||||
$this->render($content);
|
||||
$this->assertRaw($this->fieldTestData->field->getLabel(), "First field's label is displayed.");
|
||||
foreach ($values as $delta => $value) {
|
||||
$this->assertRaw("$formatter_setting|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
$this->assertRaw($this->fieldTestData->field_2->getLabel(), "Second field's label is displayed.");
|
||||
foreach ($values_2 as $delta => $value) {
|
||||
$this->assertRaw("$formatter_setting_2|{$value['value']}", "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
|
||||
// Label hidden.
|
||||
$entity = clone($entity_init);
|
||||
$display_options['label'] = 'hidden';
|
||||
$display->setComponent($this->fieldTestData->field_name, $display_options);
|
||||
$content = $display->build($entity);
|
||||
$this->render($content);
|
||||
$this->assertNoRaw($this->fieldTestData->field->getLabel(), "Hidden label: label is not displayed.");
|
||||
|
||||
// Field hidden.
|
||||
$entity = clone($entity_init);
|
||||
$display->removeComponent($this->fieldTestData->field_name);
|
||||
$content = $display->build($entity);
|
||||
$this->render($content);
|
||||
$this->assertNoRaw($this->fieldTestData->field->getLabel(), "Hidden field: label is not displayed.");
|
||||
foreach ($values as $delta => $value) {
|
||||
$this->assertNoRaw("$formatter_setting|{$value['value']}", "Hidden field: value $delta is not displayed.");
|
||||
}
|
||||
|
||||
// Multiple formatter.
|
||||
$entity = clone($entity_init);
|
||||
$formatter_setting = $this->randomMachineName();
|
||||
$display->setComponent($this->fieldTestData->field_name, [
|
||||
'label' => 'above',
|
||||
'type' => 'field_test_multiple',
|
||||
'settings' => [
|
||||
'test_formatter_setting_multiple' => $formatter_setting,
|
||||
],
|
||||
]);
|
||||
$content = $display->build($entity);
|
||||
$this->render($content);
|
||||
$expected_output = $formatter_setting;
|
||||
foreach ($values as $delta => $value) {
|
||||
$expected_output .= "|$delta:{$value['value']}";
|
||||
}
|
||||
$this->assertRaw($expected_output, "Multiple formatter: all values are displayed, formatter settings are applied.");
|
||||
|
||||
// Test a formatter that uses hook_field_formatter_prepare_view().
|
||||
$entity = clone($entity_init);
|
||||
$formatter_setting = $this->randomMachineName();
|
||||
$display->setComponent($this->fieldTestData->field_name, [
|
||||
'label' => 'above',
|
||||
'type' => 'field_test_with_prepare_view',
|
||||
'settings' => [
|
||||
'test_formatter_setting_additional' => $formatter_setting,
|
||||
],
|
||||
]);
|
||||
$content = $display->build($entity);
|
||||
$this->render($content);
|
||||
foreach ($values as $delta => $value) {
|
||||
$expected = $formatter_setting . '|' . $value['value'] . '|' . ($value['value'] + 1);
|
||||
$this->assertRaw($expected, "Value $delta is displayed, formatter settings are applied.");
|
||||
}
|
||||
|
||||
// @todo Check display order with several fields.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests rendering fields with EntityDisplay::buildMultiple().
|
||||
*/
|
||||
public function testEntityDisplayViewMultiple(): void {
|
||||
// Use a formatter that has a prepareView() step.
|
||||
$display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay('entity_test', 'entity_test', 'full')
|
||||
->setComponent($this->fieldTestData->field_name, [
|
||||
'type' => 'field_test_with_prepare_view',
|
||||
]);
|
||||
|
||||
// Create two entities.
|
||||
$entity1 = EntityTest::create(['id' => 1, 'type' => 'entity_test']);
|
||||
$entity1->{$this->fieldTestData->field_name}->setValue($this->_generateTestFieldValues(1));
|
||||
$entity2 = EntityTest::create(['id' => 2, 'type' => 'entity_test']);
|
||||
$entity2->{$this->fieldTestData->field_name}->setValue($this->_generateTestFieldValues(1));
|
||||
|
||||
// Run buildMultiple(), and check that the entities come out as expected.
|
||||
$display->buildMultiple([$entity1, $entity2]);
|
||||
$item1 = $entity1->{$this->fieldTestData->field_name}[0];
|
||||
$this->assertEquals($item1->value + 1, $item1->additional_formatter_value, 'Entity 1 ran through the prepareView() formatter method.');
|
||||
$item2 = $entity2->{$this->fieldTestData->field_name}[0];
|
||||
$this->assertEquals($item2->value + 1, $item2->additional_formatter_value, 'Entity 2 ran through the prepareView() formatter method.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity cache.
|
||||
*
|
||||
* Complements unit test coverage in
|
||||
* \Drupal\Tests\Core\Entity\Sql\SqlContentEntityStorageTest.
|
||||
*/
|
||||
public function testEntityCache(): void {
|
||||
// Initialize random values and a test entity.
|
||||
$entity_init = EntityTest::create(['type' => $this->fieldTestData->field->getTargetBundle()]);
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
|
||||
|
||||
// Non-cacheable entity type.
|
||||
$entity_type = 'entity_test';
|
||||
$cid = "values:$entity_type:" . $entity_init->id();
|
||||
|
||||
// Check that no initial cache entry is present.
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no initial cache entry');
|
||||
|
||||
// Save, and check that no cache entry is present.
|
||||
$entity = clone($entity_init);
|
||||
$entity->{$this->fieldTestData->field_name}->setValue($values);
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$cid = "values:$entity_type:" . $entity->id();
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no cache entry on insert and load');
|
||||
|
||||
// Cacheable entity type.
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('_2', $entity_type);
|
||||
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create([
|
||||
'type' => $entity_type,
|
||||
]);
|
||||
|
||||
// Check that no initial cache entry is present.
|
||||
$cid = "values:$entity_type:" . $entity->id();
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no initial cache entry');
|
||||
|
||||
// Save, and check that no cache entry is present.
|
||||
$entity = clone($entity_init);
|
||||
$entity->{$this->fieldTestData->field_name_2} = $values;
|
||||
$entity->save();
|
||||
$cid = "values:$entity_type:" . $entity->id();
|
||||
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on insert');
|
||||
// Load, and check that a cache entry is present with the expected values.
|
||||
$controller = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$controller->resetCache();
|
||||
$cached_entity = $controller->load($entity->id());
|
||||
$cache = \Drupal::cache('entity')->get($cid);
|
||||
$this->assertEquals($cached_entity, $cache->data, 'Cached: correct cache entry on load');
|
||||
|
||||
// Update with different values, and check that the cache entry is wiped.
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
|
||||
$entity->{$this->fieldTestData->field_name_2} = $values;
|
||||
$entity->save();
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on update');
|
||||
|
||||
// Load, and check that a cache entry is present with the expected values.
|
||||
$controller->resetCache();
|
||||
$cached_entity = $controller->load($entity->id());
|
||||
$cache = \Drupal::cache('entity')->get($cid);
|
||||
$this->assertEquals($cached_entity, $cache->data, 'Cached: correct cache entry on load');
|
||||
|
||||
// Create a new revision, and check that the cache entry is wiped.
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
|
||||
$entity->{$this->fieldTestData->field_name_2} = $values;
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on new revision creation');
|
||||
|
||||
// Load, and check that a cache entry is present with the expected values.
|
||||
$controller->resetCache();
|
||||
$cached_entity = $controller->load($entity->id());
|
||||
$cache = \Drupal::cache('entity')->get($cid);
|
||||
$this->assertEquals($cached_entity, $cache->data, 'Cached: correct cache entry on load');
|
||||
|
||||
// Delete, and check that the cache entry is wiped.
|
||||
$entity->delete();
|
||||
$this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry after delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::buildForm().
|
||||
*
|
||||
* This could be much more thorough, but it does verify that the correct
|
||||
* widgets show up.
|
||||
*/
|
||||
public function testEntityFormDisplayBuildForm(): void {
|
||||
$this->createFieldWithStorage('_2');
|
||||
|
||||
$entity_type = 'entity_test';
|
||||
$entity = \Drupal::entityTypeManager()->getStorage($entity_type)->create([
|
||||
'id' => 1,
|
||||
'revision_id' => 1,
|
||||
'type' => $this->fieldTestData->field->getTargetBundle(),
|
||||
]);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Test generating widgets for all fields.
|
||||
$display = $display_repository->getFormDisplay($entity_type, $this->fieldTestData->field->getTargetBundle());
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$display->buildForm($entity, $form, $form_state);
|
||||
|
||||
$this->assertEquals($this->fieldTestData->field->getLabel(), $form[$this->fieldTestData->field_name]['widget']['#title'], "First field's form title is {$this->fieldTestData->field->getLabel()}");
|
||||
$this->assertEquals($this->fieldTestData->field_2->getLabel(), $form[$this->fieldTestData->field_name_2]['widget']['#title'], "Second field's form title is {$this->fieldTestData->field_2->getLabel()}");
|
||||
for ($delta = 0; $delta < $this->fieldTestData->field_storage->getCardinality(); $delta++) {
|
||||
// field_test_widget uses 'textfield'
|
||||
$this->assertEquals('textfield', $form[$this->fieldTestData->field_name]['widget'][$delta]['value']['#type'], "First field's form delta {$delta} widget is textfield");
|
||||
}
|
||||
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
|
||||
// field_test_widget uses 'textfield'
|
||||
$this->assertEquals('textfield', $form[$this->fieldTestData->field_name_2]['widget'][$delta]['value']['#type'], "Second field's form delta {$delta} widget is textfield");
|
||||
}
|
||||
|
||||
// Test generating widgets for all fields.
|
||||
$display = $display_repository->getFormDisplay($entity_type, $this->fieldTestData->field->getTargetBundle());
|
||||
foreach ($display->getComponents() as $name => $options) {
|
||||
if ($name != $this->fieldTestData->field_name_2) {
|
||||
$display->removeComponent($name);
|
||||
}
|
||||
}
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$display->buildForm($entity, $form, $form_state);
|
||||
|
||||
$this->assertFalse(isset($form[$this->fieldTestData->field_name]), 'The first field does not exist in the form');
|
||||
$this->assertEquals($this->fieldTestData->field_2->getLabel(), $form[$this->fieldTestData->field_name_2]['widget']['#title'], "Second field's form title is {$this->fieldTestData->field_2->getLabel()}");
|
||||
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
|
||||
// field_test_widget uses 'textfield'
|
||||
$this->assertEquals('textfield', $form[$this->fieldTestData->field_name_2]['widget'][$delta]['value']['#type'], "Second field's form delta {$delta} widget is textfield");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Entity\Display\EntityFormDisplayInterface::extractFormValues().
|
||||
*/
|
||||
public function testEntityFormDisplayExtractFormValues(): void {
|
||||
$this->createFieldWithStorage('_2');
|
||||
|
||||
$entity_type = 'entity_test';
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => 1, 'revision_id' => 1, 'type' => $this->fieldTestData->field->getTargetBundle()]);
|
||||
|
||||
// Build the form for all fields.
|
||||
$display = \Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->fieldTestData->field->getTargetBundle());
|
||||
$form = [];
|
||||
$form_state = new FormState();
|
||||
$display->buildForm($entity_init, $form, $form_state);
|
||||
|
||||
// Simulate incoming values.
|
||||
// First field.
|
||||
$values = [];
|
||||
$weights = [];
|
||||
for ($delta = 0; $delta < $this->fieldTestData->field_storage->getCardinality(); $delta++) {
|
||||
$values[$delta]['value'] = mt_rand(1, 127);
|
||||
// Assign random weight.
|
||||
do {
|
||||
$weight = mt_rand(0, $this->fieldTestData->field_storage->getCardinality());
|
||||
} while (in_array($weight, $weights));
|
||||
$weights[$delta] = $weight;
|
||||
$values[$delta]['_weight'] = $weight;
|
||||
}
|
||||
// Leave an empty value. 'field_test' fields are empty if empty().
|
||||
$values[1]['value'] = 0;
|
||||
// Second field.
|
||||
$values_2 = [];
|
||||
$weights_2 = [];
|
||||
for ($delta = 0; $delta < $this->fieldTestData->field_storage_2->getCardinality(); $delta++) {
|
||||
$values_2[$delta]['value'] = mt_rand(1, 127);
|
||||
// Assign random weight.
|
||||
do {
|
||||
$weight = mt_rand(0, $this->fieldTestData->field_storage_2->getCardinality());
|
||||
} while (in_array($weight, $weights_2));
|
||||
$weights_2[$delta] = $weight;
|
||||
$values_2[$delta]['_weight'] = $weight;
|
||||
}
|
||||
// Leave an empty value. 'field_test' fields are empty if empty().
|
||||
$values_2[1]['value'] = 0;
|
||||
|
||||
// Pretend the form has been built.
|
||||
$form_state->setFormObject(\Drupal::entityTypeManager()->getFormObject($entity_type, 'default'));
|
||||
\Drupal::formBuilder()->prepareForm('field_test_entity_form', $form, $form_state);
|
||||
\Drupal::formBuilder()->processForm('field_test_entity_form', $form, $form_state);
|
||||
$form_state->setValue($this->fieldTestData->field_name, $values);
|
||||
$form_state->setValue($this->fieldTestData->field_name_2, $values_2);
|
||||
|
||||
// Extract values for all fields.
|
||||
$entity = clone($entity_init);
|
||||
$display->extractFormValues($entity, $form, $form_state);
|
||||
|
||||
asort($weights);
|
||||
asort($weights_2);
|
||||
$expected_values = [];
|
||||
$expected_values_2 = [];
|
||||
foreach ($weights as $key => $value) {
|
||||
if ($key != 1) {
|
||||
$expected_values[] = ['value' => $values[$key]['value']];
|
||||
}
|
||||
}
|
||||
$this->assertSame($expected_values, $entity->{$this->fieldTestData->field_name}->getValue(), 'Submit filters empty values');
|
||||
foreach ($weights_2 as $key => $value) {
|
||||
if ($key != 1) {
|
||||
$expected_values_2[] = ['value' => $values_2[$key]['value']];
|
||||
}
|
||||
}
|
||||
$this->assertSame($expected_values_2, $entity->{$this->fieldTestData->field_name_2}->getValue(), 'Submit filters empty values');
|
||||
|
||||
// Call EntityFormDisplayInterface::extractFormValues() for a single field
|
||||
// (the second field).
|
||||
foreach ($display->getComponents() as $name => $options) {
|
||||
if ($name != $this->fieldTestData->field_name_2) {
|
||||
$display->removeComponent($name);
|
||||
}
|
||||
}
|
||||
$entity = clone($entity_init);
|
||||
$display->extractFormValues($entity, $form, $form_state);
|
||||
$expected_values_2 = [];
|
||||
foreach ($weights_2 as $key => $value) {
|
||||
if ($key != 1) {
|
||||
$expected_values_2[] = ['value' => $values_2[$key]['value']];
|
||||
}
|
||||
}
|
||||
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'The first field is empty in the entity object');
|
||||
$this->assertSame($expected_values_2, $entity->{$this->fieldTestData->field_name_2}->getValue(), 'Submit filters empty values');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,389 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
|
||||
/**
|
||||
* Tests storage-related Field Attach API functions.
|
||||
*
|
||||
* @group field
|
||||
* @todo move this to the Entity module
|
||||
*/
|
||||
class FieldAttachStorageTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check field values insert, update and load.
|
||||
*
|
||||
* Works independently of the underlying field storage backend. Inserts or
|
||||
* updates random field data and then loads and verifies the data.
|
||||
*/
|
||||
public function testFieldAttachSaveLoad(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
$cardinality = $this->fieldTestData->field_storage->getCardinality();
|
||||
|
||||
// @todo Test empty values filtering and "compression" (store consecutive deltas).
|
||||
// Preparation: create three revisions and store them in $revision array.
|
||||
$values = [];
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create();
|
||||
for ($revision_id = 0; $revision_id < 3; $revision_id++) {
|
||||
// Note: we try to insert one extra value.
|
||||
$current_values = $this->_generateTestFieldValues($cardinality + 1);
|
||||
$entity->{$this->fieldTestData->field_name}->setValue($current_values);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$entity_id = $entity->id();
|
||||
$current_revision = $entity->getRevisionId();
|
||||
$values[$current_revision] = $current_values;
|
||||
}
|
||||
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
|
||||
$storage->resetCache();
|
||||
$entity = $storage->load($entity_id);
|
||||
// Confirm current revision loads the correct data.
|
||||
// Number of values per field loaded equals the field cardinality.
|
||||
$this->assertCount($cardinality, $entity->{$this->fieldTestData->field_name}, 'Current revision: expected number of values');
|
||||
for ($delta = 0; $delta < $cardinality; $delta++) {
|
||||
// The field value loaded matches the one inserted or updated.
|
||||
$this->assertEquals($values[$current_revision][$delta]['value'], $entity->{$this->fieldTestData->field_name}[$delta]->value, "Current revision: expected value $delta was found.");
|
||||
}
|
||||
|
||||
// Confirm each revision loads the correct data.
|
||||
foreach (array_keys($values) as $revision_id) {
|
||||
$entity = $storage->loadRevision($revision_id);
|
||||
// Number of values per field loaded equals the field cardinality.
|
||||
$this->assertCount($cardinality, $entity->{$this->fieldTestData->field_name}, "Revision $revision_id: expected number of values.");
|
||||
for ($delta = 0; $delta < $cardinality; $delta++) {
|
||||
// The field value loaded matches the one inserted or updated.
|
||||
$this->assertEquals($values[$revision_id][$delta]['value'], $entity->{$this->fieldTestData->field_name}[$delta]->value, "Revision $revision_id: expected value $delta was found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the 'multiple' load feature.
|
||||
*/
|
||||
public function testFieldAttachLoadMultiple(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
|
||||
// Define 2 bundles.
|
||||
$bundles = [
|
||||
1 => 'test_bundle_1',
|
||||
2 => 'test_bundle_2',
|
||||
];
|
||||
EntityTestHelper::createBundle($bundles[1], entity_type: $entity_type);
|
||||
EntityTestHelper::createBundle($bundles[2], entity_type: $entity_type);
|
||||
// Define 3 fields:
|
||||
// - field_1 is in bundle_1 and bundle_2,
|
||||
// - field_2 is in bundle_1,
|
||||
// - field_3 is in bundle_2.
|
||||
$field_bundles_map = [
|
||||
1 => [1, 2],
|
||||
2 => [1],
|
||||
3 => [2],
|
||||
];
|
||||
for ($i = 1; $i <= 3; $i++) {
|
||||
$field_names[$i] = 'field_' . $i;
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_names[$i],
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
foreach ($field_bundles_map[$i] as $bundle) {
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_names[$i],
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $bundles[$bundle],
|
||||
])->save();
|
||||
}
|
||||
}
|
||||
|
||||
// Create one test entity per bundle, with random values.
|
||||
foreach ($bundles as $index => $bundle) {
|
||||
$entities[$index] = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => $index, 'revision_id' => $index, 'type' => $bundle]);
|
||||
$entity = clone($entities[$index]);
|
||||
foreach ($field_names as $field_name) {
|
||||
if (!$entity->hasField($field_name)) {
|
||||
continue;
|
||||
}
|
||||
$values[$index][$field_name] = mt_rand(1, 127);
|
||||
$entity->$field_name->setValue(['value' => $values[$index][$field_name]]);
|
||||
}
|
||||
$entity->enforceIsNew();
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
// Check that a single load correctly loads field values for both entities.
|
||||
$controller = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
|
||||
$controller->resetCache();
|
||||
$entities = $controller->loadMultiple();
|
||||
foreach ($entities as $index => $entity) {
|
||||
foreach ($field_names as $field_name) {
|
||||
if (!$entity->hasField($field_name)) {
|
||||
continue;
|
||||
}
|
||||
// The field value loaded matches the one inserted.
|
||||
$this->assertEquals($values[$index][$field_name], $entity->{$field_name}->value, "Entity $index: expected value was found.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests insert and update with empty or NULL fields.
|
||||
*/
|
||||
public function testFieldAttachSaveEmptyData(): void {
|
||||
$entity_type = 'entity_test';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => 1]);
|
||||
|
||||
// Insert: Field is NULL.
|
||||
$entity = clone $entity_init;
|
||||
$entity->{$this->fieldTestData->field_name} = NULL;
|
||||
$entity->enforceIsNew();
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
|
||||
|
||||
// All saves after this point should be updates, not inserts.
|
||||
$entity_init->enforceIsNew(FALSE);
|
||||
|
||||
// Add some real data.
|
||||
$entity = clone($entity_init);
|
||||
$values = $this->_generateTestFieldValues(1);
|
||||
$entity->{$this->fieldTestData->field_name} = $values;
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertEquals($values, $entity->{$this->fieldTestData->field_name}->getValue(), 'Field data saved');
|
||||
|
||||
// Update: Field is NULL. Data should be wiped.
|
||||
$entity = clone($entity_init);
|
||||
$entity->{$this->fieldTestData->field_name} = NULL;
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Update: NULL field removes existing values');
|
||||
|
||||
// Re-add some data.
|
||||
$entity = clone($entity_init);
|
||||
$values = $this->_generateTestFieldValues(1);
|
||||
$entity->{$this->fieldTestData->field_name} = $values;
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertEquals($values, $entity->{$this->fieldTestData->field_name}->getValue(), 'Field data saved');
|
||||
|
||||
// Update: Field is empty array. Data should be wiped.
|
||||
$entity = clone($entity_init);
|
||||
$entity->{$this->fieldTestData->field_name} = [];
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Update: empty array removes existing values');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests insert with empty or NULL fields, with default value.
|
||||
*/
|
||||
public function testFieldAttachSaveEmptyDataDefaultValue(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
|
||||
// Add a default value function.
|
||||
$this->fieldTestData->field->set('default_value_callback', FieldTestHelper::class . '::defaultValue');
|
||||
$this->fieldTestData->field->save();
|
||||
|
||||
// Verify that fields are populated with default values.
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['id' => 1, 'revision_id' => 1]);
|
||||
$default = FieldTestHelper::defaultValue($entity_init, $this->fieldTestData->field);
|
||||
$this->assertEquals($default, $entity_init->{$this->fieldTestData->field_name}->getValue(), 'Default field value correctly populated.');
|
||||
|
||||
// Insert: Field is NULL.
|
||||
$entity = clone($entity_init);
|
||||
$entity->{$this->fieldTestData->field_name} = NULL;
|
||||
$entity->enforceIsNew();
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertTrue($entity->{$this->fieldTestData->field_name}->isEmpty(), 'Insert: NULL field results in no value saved');
|
||||
|
||||
// Verify that prepopulated field values are not overwritten by defaults.
|
||||
$value = [['value' => $default[0]['value'] - mt_rand(1, 127)]];
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['type' => $entity_init->bundle(), $this->fieldTestData->field_name => $value]);
|
||||
$this->assertEquals($value, $entity->{$this->fieldTestData->field_name}->getValue(), 'Prepopulated field value correctly maintained.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity deletion.
|
||||
*/
|
||||
public function testFieldAttachDelete(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
$cardinality = $this->fieldTestData->field_storage->getCardinality();
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['type' => $this->fieldTestData->field->getTargetBundle()]);
|
||||
$vids = [];
|
||||
|
||||
// Create revision 0
|
||||
$values = $this->_generateTestFieldValues($cardinality);
|
||||
$entity->{$this->fieldTestData->field_name} = $values;
|
||||
$entity->save();
|
||||
$vids[] = $entity->getRevisionId();
|
||||
|
||||
// Create revision 1
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$vids[] = $entity->getRevisionId();
|
||||
|
||||
// Create revision 2
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$vids[] = $entity->getRevisionId();
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $controller */
|
||||
$controller = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$controller->resetCache();
|
||||
|
||||
// Confirm each revision loads
|
||||
foreach ($vids as $vid) {
|
||||
$revision = $controller->loadRevision($vid);
|
||||
$this->assertCount($cardinality, $revision->{$this->fieldTestData->field_name}, "The test entity revision $vid has $cardinality values.");
|
||||
}
|
||||
|
||||
// Delete revision 1, confirm the other two still load.
|
||||
$controller->deleteRevision($vids[1]);
|
||||
$controller->resetCache();
|
||||
foreach ([0, 2] as $key) {
|
||||
$vid = $vids[$key];
|
||||
$revision = $controller->loadRevision($vid);
|
||||
$this->assertCount($cardinality, $revision->{$this->fieldTestData->field_name}, "The test entity revision $vid has $cardinality values.");
|
||||
}
|
||||
|
||||
// Confirm the current revision still loads
|
||||
$controller->resetCache();
|
||||
$current = $controller->load($entity->id());
|
||||
$this->assertCount($cardinality, $current->{$this->fieldTestData->field_name}, "The test entity current revision has $cardinality values.");
|
||||
|
||||
// Delete all field data, confirm nothing loads
|
||||
$entity->delete();
|
||||
$controller->resetCache();
|
||||
foreach ([0, 1, 2] as $vid) {
|
||||
$revision = $controller->loadRevision($vid);
|
||||
$this->assertNull($revision);
|
||||
}
|
||||
$this->assertNull($controller->load($entity->id()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity_bundle_create().
|
||||
*/
|
||||
public function testEntityCreateBundle(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
$cardinality = $this->fieldTestData->field_storage->getCardinality();
|
||||
|
||||
// Create a new bundle.
|
||||
$new_bundle = 'test_bundle_' . $this->randomMachineName();
|
||||
EntityTestHelper::createBundle($new_bundle, NULL, $entity_type);
|
||||
|
||||
// Add a field to that bundle.
|
||||
$this->fieldTestData->field_definition['bundle'] = $new_bundle;
|
||||
FieldConfig::create($this->fieldTestData->field_definition)->save();
|
||||
|
||||
// Save an entity with data in the field.
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['type' => $this->fieldTestData->field->getTargetBundle()]);
|
||||
$values = $this->_generateTestFieldValues($cardinality);
|
||||
$entity->{$this->fieldTestData->field_name} = $values;
|
||||
|
||||
// Verify the field data is present on load.
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertCount($cardinality, $entity->{$this->fieldTestData->field_name}, "Data is retrieved for the new bundle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entity_bundle_delete().
|
||||
*/
|
||||
public function testEntityDeleteBundle(): void {
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('', $entity_type);
|
||||
|
||||
// Create a new bundle.
|
||||
$new_bundle = 'test_bundle_' . $this->randomMachineName();
|
||||
EntityTestHelper::createBundle($new_bundle, NULL, $entity_type);
|
||||
|
||||
// Add a field to that bundle.
|
||||
$this->fieldTestData->field_definition['bundle'] = $new_bundle;
|
||||
FieldConfig::create($this->fieldTestData->field_definition)->save();
|
||||
|
||||
// Create a second field for the test bundle
|
||||
$field_name = $this->randomMachineName() . '_field_name';
|
||||
$field_storage = [
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
];
|
||||
FieldStorageConfig::create($field_storage)->save();
|
||||
$field = [
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'bundle' => $this->fieldTestData->field->getTargetBundle(),
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => $this->randomMachineName() . '_description',
|
||||
'weight' => mt_rand(0, 127),
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
|
||||
// Save an entity with data for both fields
|
||||
$entity = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['type' => $this->fieldTestData->field->getTargetBundle()]);
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage->getCardinality());
|
||||
$entity->{$this->fieldTestData->field_name} = $values;
|
||||
$entity->{$field_name} = $this->_generateTestFieldValues(1);
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
|
||||
// Verify the fields are present on load
|
||||
$this->assertCount(4, $entity->{$this->fieldTestData->field_name}, 'First field got loaded');
|
||||
$this->assertCount(1, $entity->{$field_name}, 'Second field got loaded');
|
||||
|
||||
// Delete the bundle. The form display has to be deleted first to prevent
|
||||
// schema errors when fields attached to the deleted bundle are themselves
|
||||
// deleted, which triggers an update of the form display.
|
||||
$this->container->get('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $this->fieldTestData->field->getTargetBundle())
|
||||
->delete();
|
||||
EntityTestHelper::deleteBundle($this->fieldTestData->field->getTargetBundle(), $entity_type);
|
||||
|
||||
// Verify no data gets loaded
|
||||
$controller = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$controller->resetCache();
|
||||
$entity = $controller->load($entity->id());
|
||||
|
||||
$this->assertEmpty($entity->{$this->fieldTestData->field_name}, 'No data for first field');
|
||||
$this->assertEmpty($entity->{$field_name}, 'No data for second field');
|
||||
|
||||
// Verify that the fields are gone.
|
||||
$this->assertNull(FieldConfig::load('entity_test.' . $this->fieldTestData->field->getTargetBundle() . '.' . $this->fieldTestData->field_name), "First field is deleted");
|
||||
$this->assertNull(FieldConfig::load('entity_test.' . $field['bundle'] . '.' . $field_name), "Second field is deleted");
|
||||
}
|
||||
|
||||
}
|
||||
364
web/core/modules/field/tests/src/Kernel/FieldCrudTest.php
Normal file
364
web/core/modules/field/tests/src/Kernel/FieldCrudTest.php
Normal file
@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Field\FieldException;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
/**
|
||||
* Create field entities by attaching fields to entities.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\Core\Field\FieldConfigBase
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldCrudTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* The field storage entity.
|
||||
*
|
||||
* @var \Drupal\field\Entity\FieldStorageConfig
|
||||
*/
|
||||
protected $fieldStorage;
|
||||
|
||||
/**
|
||||
* The field entity definition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldStorageDefinition;
|
||||
|
||||
/**
|
||||
* The field entity definition.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldDefinition;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldStorageDefinition = [
|
||||
'field_name' => $this->randomMachineName(),
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$this->fieldStorage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
||||
$this->fieldStorage->save();
|
||||
$this->fieldDefinition = [
|
||||
'field_name' => $this->fieldStorage->getName(),
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
];
|
||||
}
|
||||
|
||||
// @todo Test creation with
|
||||
// - a full fledged $field structure, check that all the values are there
|
||||
// - a minimal $field structure, check all default values are set
|
||||
// defer actual $field comparison to a helper function, used for the two cases above,
|
||||
// and for testUpdateField
|
||||
|
||||
/**
|
||||
* Tests the creation of a field.
|
||||
*/
|
||||
public function testCreateField(): void {
|
||||
$field = FieldConfig::create($this->fieldDefinition);
|
||||
$field->save();
|
||||
|
||||
$field = FieldConfig::load($field->id());
|
||||
$this->assertEquals('TRUE', $field->getSetting('field_setting_from_config_data'));
|
||||
$this->assertNull($field->getSetting('config_data_from_field_setting'));
|
||||
|
||||
// Read the configuration. Check against raw configuration data rather than
|
||||
// the loaded ConfigEntity, to be sure we check that the defaults are
|
||||
// applied on write.
|
||||
$config = $this->config('field.field.' . $field->id())->get();
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
|
||||
$this->assertTrue($config['settings']['config_data_from_field_setting']);
|
||||
$this->assertTrue(!isset($config['settings']['field_setting_from_config_data']));
|
||||
|
||||
// Since we are working with raw configuration, this needs to be unset
|
||||
// manually.
|
||||
// @see Drupal\field_test\Plugin\Field\FieldType\TestItem::fieldSettingsFromConfigData()
|
||||
unset($config['settings']['config_data_from_field_setting']);
|
||||
|
||||
// Check that default values are set.
|
||||
$this->assertFalse($config['required'], 'Required defaults to false.');
|
||||
$this->assertSame($config['label'], $this->fieldDefinition['field_name'], 'Label defaults to field name.');
|
||||
$this->assertSame('', $config['description'], 'Description defaults to empty string.');
|
||||
|
||||
// Check that default settings are set.
|
||||
$this->assertEquals($config['settings'], $field_type_manager->getDefaultFieldSettings($this->fieldStorageDefinition['type']), 'Default field settings have been written.');
|
||||
|
||||
// Check that the denormalized 'field_type' was properly written.
|
||||
$this->assertEquals($config['field_type'], $this->fieldStorageDefinition['type']);
|
||||
|
||||
// Guarantee that the field/bundle combination is unique.
|
||||
try {
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
$this->fail('Cannot create two fields with the same field / bundle combination.');
|
||||
}
|
||||
catch (EntityStorageException) {
|
||||
// Expected exception; just continue testing.
|
||||
}
|
||||
|
||||
// Check that the specified field exists.
|
||||
try {
|
||||
$this->fieldDefinition['field_name'] = $this->randomMachineName();
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
$this->fail('Cannot create a field with a non-existing storage.');
|
||||
}
|
||||
catch (FieldException) {
|
||||
// Expected exception; just continue testing.
|
||||
}
|
||||
|
||||
// @todo Test other failures.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests setting and adding property constraints to a configurable field.
|
||||
*
|
||||
* @covers ::setPropertyConstraints
|
||||
* @covers ::addPropertyConstraints
|
||||
*/
|
||||
public function testFieldPropertyConstraints(): void {
|
||||
$field = FieldConfig::create($this->fieldDefinition);
|
||||
$field->save();
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
|
||||
// Test that constraints are applied to configurable fields. A TestField and
|
||||
// a Range constraint are added dynamically to limit the field to values
|
||||
// between 0 and 32.
|
||||
// @see field_test_entity_bundle_field_info_alter()
|
||||
\Drupal::state()->set('field_test_constraint', $field_name);
|
||||
|
||||
// Clear the field definitions cache so the new constraints added by
|
||||
// field_test_entity_bundle_field_info_alter() are taken into consideration.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Test the newly added property constraints in the same request as when the
|
||||
// caches were cleared. This will test the field definitions that are stored
|
||||
// in the static cache of
|
||||
// \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions().
|
||||
$this->doFieldPropertyConstraintsTests();
|
||||
|
||||
// In order to test a real-world scenario where the property constraints are
|
||||
// only stored in the persistent cache of
|
||||
// \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions(), we need to
|
||||
// simulate a new request by removing the 'entity_field.manager' service,
|
||||
// thus forcing it to be re-initialized without static caches.
|
||||
\Drupal::getContainer()->set('entity_field.manager', NULL);
|
||||
|
||||
// This will test the field definitions that are stored in the persistent
|
||||
// cache by \Drupal\Core\Entity\EntityFieldManager::getFieldDefinitions().
|
||||
$this->doFieldPropertyConstraintsTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests configurable field validation.
|
||||
*
|
||||
* @see field_test_entity_bundle_field_info_alter()
|
||||
*/
|
||||
protected function doFieldPropertyConstraintsTests(): void {
|
||||
$field_name = $this->fieldStorage->getName();
|
||||
|
||||
// Check that a valid value (not -2 and between 0 and 32) doesn't trigger
|
||||
// any violation.
|
||||
$entity = EntityTest::create();
|
||||
$entity->set($field_name, 1);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(0, $violations, 'No violations found when in-range value passed.');
|
||||
|
||||
// Check that a value that is specifically restricted triggers both
|
||||
// violations.
|
||||
$entity->set($field_name, -2);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(2, $violations, 'Two violations found when using a null and outside the range value.');
|
||||
|
||||
$this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath());
|
||||
$this->assertEquals(sprintf('%s does not accept the value -2.', $field_name), $violations[0]->getMessage());
|
||||
|
||||
$this->assertEquals($field_name . '.0.value', $violations[1]->getPropertyPath());
|
||||
$this->assertEquals('This value should be between 0 and 32.', $violations[1]->getMessage());
|
||||
|
||||
// Check that a value that is not specifically restricted but outside the
|
||||
// range triggers the expected violation.
|
||||
$entity->set($field_name, 33);
|
||||
$violations = $entity->validate();
|
||||
$this->assertCount(1, $violations, 'Violations found when using value outside the range.');
|
||||
$this->assertEquals($field_name . '.0.value', $violations[0]->getPropertyPath());
|
||||
$this->assertEquals('This value should be between 0 and 32.', $violations[0]->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating a field with custom storage set.
|
||||
*/
|
||||
public function testCreateFieldCustomStorage(): void {
|
||||
$field_name = $this->randomMachineName();
|
||||
\Drupal::state()->set('field_test_custom_storage', $field_name);
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'custom_storage' => TRUE,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_storage->getName(),
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Check that no table has been created for the field.
|
||||
$this->assertFalse(\Drupal::database()->schema()->tableExists('entity_test__' . $field_storage->getName()));
|
||||
|
||||
// Save an entity with a value in the custom storage field and verify no
|
||||
// data is retrieved on load.
|
||||
$entity = EntityTest::create(['name' => $this->randomString(), $field_name => 'Test value']);
|
||||
$this->assertSame('Test value', $entity->{$field_name}->value, 'The test value is set on the field.');
|
||||
|
||||
$entity->save();
|
||||
$entity = EntityTest::load($entity->id());
|
||||
|
||||
$this->assertNull($entity->{$field_name}->value, 'The loaded entity field value is NULL.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reading back a field definition.
|
||||
*/
|
||||
public function testReadField(): void {
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
|
||||
// Read the field back.
|
||||
$field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
||||
$this->assertSame($this->fieldDefinition['field_name'], $field->getName(), 'The field was properly read.');
|
||||
$this->assertSame($this->fieldDefinition['entity_type'], $field->getTargetEntityTypeId(), 'The field was properly read.');
|
||||
$this->assertSame($this->fieldDefinition['bundle'], $field->getTargetBundle(), 'The field was properly read.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the update of a field.
|
||||
*/
|
||||
public function testUpdateField(): void {
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
|
||||
// Check that basic changes are saved.
|
||||
$field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
||||
$field->setRequired(!$field->isRequired());
|
||||
$field->setLabel($this->randomMachineName());
|
||||
$field->set('description', $this->randomMachineName());
|
||||
$field->setSetting('test_field_setting', $this->randomMachineName());
|
||||
$field->save();
|
||||
|
||||
$field_new = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
||||
$this->assertEquals($field->isRequired(), $field_new->isRequired(), '"required" change is saved');
|
||||
$this->assertEquals($field->getLabel(), $field_new->getLabel(), '"label" change is saved');
|
||||
$this->assertEquals($field->getDescription(), $field_new->getDescription(), '"description" change is saved');
|
||||
|
||||
// @todo Test failures.
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deletion of a field with no data.
|
||||
*/
|
||||
public function testDeleteFieldNoData(): void {
|
||||
// Deleting and purging fields with data is tested in
|
||||
// \Drupal\Tests\field\Kernel\BulkDeleteTest.
|
||||
|
||||
// Create two fields for the same field storage so we can test that only one
|
||||
// is deleted.
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
$another_field_definition = $this->fieldDefinition;
|
||||
$another_field_definition['bundle'] .= '_another_bundle';
|
||||
EntityTestHelper::createBundle($another_field_definition['bundle']);
|
||||
FieldConfig::create($another_field_definition)->save();
|
||||
|
||||
// Test that the first field is not deleted, and then delete it.
|
||||
$field = current(\Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => $this->fieldDefinition['field_name'],
|
||||
'bundle' => $this->fieldDefinition['bundle'],
|
||||
'include_deleted' => TRUE,
|
||||
]));
|
||||
$this->assertFalse($field->isDeleted());
|
||||
$field->delete();
|
||||
|
||||
// Make sure the field was deleted without being marked for purging as there
|
||||
// was no data.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => $this->fieldDefinition['field_name'],
|
||||
'bundle' => $this->fieldDefinition['bundle'],
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $fields, 'A deleted field is marked for deletion.');
|
||||
|
||||
// Try to load the field normally and make sure it does not show up.
|
||||
$field = FieldConfig::load('entity_test.' . $this->fieldDefinition['bundle'] . '.' . $this->fieldDefinition['field_name']);
|
||||
$this->assertTrue(empty($field), 'Field was deleted');
|
||||
|
||||
// Make sure the other field is not deleted.
|
||||
$another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
|
||||
$this->assertFalse($another_field->isDeleted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the cross deletion behavior between field storages and fields.
|
||||
*/
|
||||
public function testDeleteFieldCrossDeletion(): void {
|
||||
$field_definition_2 = $this->fieldDefinition;
|
||||
$field_definition_2['bundle'] .= '_another_bundle';
|
||||
EntityTestHelper::createBundle($field_definition_2['bundle']);
|
||||
|
||||
// Check that deletion of a field storage deletes its fields.
|
||||
$field_storage = $this->fieldStorage;
|
||||
FieldConfig::create($this->fieldDefinition)->save();
|
||||
FieldConfig::create($field_definition_2)->save();
|
||||
$field_storage->delete();
|
||||
$this->assertNull(FieldConfig::loadByName('entity_test', $this->fieldDefinition['bundle'], $field_storage->getName()));
|
||||
$this->assertNull(FieldConfig::loadByName('entity_test', $field_definition_2['bundle'], $field_storage->getName()));
|
||||
|
||||
// Check that deletion of the last field deletes the storage.
|
||||
$field_storage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create($this->fieldDefinition);
|
||||
$field->save();
|
||||
$field_2 = FieldConfig::create($field_definition_2);
|
||||
$field_2->save();
|
||||
$field->delete();
|
||||
$this->assertNotEmpty(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
||||
$field_2->delete();
|
||||
$this->assertNull(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
||||
|
||||
// Check that deletion of all fields using a storage simultaneously deletes
|
||||
// the storage.
|
||||
$field_storage = FieldStorageConfig::create($this->fieldStorageDefinition);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create($this->fieldDefinition);
|
||||
$field->save();
|
||||
$field_2 = FieldConfig::create($field_definition_2);
|
||||
$field_2->save();
|
||||
$this->container->get('entity_type.manager')->getStorage('field_config')->delete([$field, $field_2]);
|
||||
$this->assertNull(FieldStorageConfig::loadByName('entity_test', $field_storage->getName()));
|
||||
}
|
||||
|
||||
}
|
||||
184
web/core/modules/field/tests/src/Kernel/FieldDataCountTest.php
Normal file
184
web/core/modules/field/tests/src/Kernel/FieldDataCountTest.php
Normal file
@ -0,0 +1,184 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the count of field data records.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\Core\Entity\FieldableEntityStorageInterface::countFieldData()
|
||||
* @see \Drupal\field\Entity\FieldStorageConfig::hasData()
|
||||
*/
|
||||
class FieldDataCountTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface
|
||||
*/
|
||||
protected $storageRev;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\DynamicallyFieldableEntityStorageInterface
|
||||
*/
|
||||
protected $storageUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->storage = \Drupal::entityTypeManager()->getStorage('entity_test');
|
||||
$this->storageRev = \Drupal::entityTypeManager()->getStorage('entity_test_rev');
|
||||
$this->storageUser = \Drupal::entityTypeManager()->getStorage('user');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests entityCount() and hadData() methods.
|
||||
*/
|
||||
public function testEntityCountAndHasData(): void {
|
||||
// Create a field with a cardinality of 2 to show that we are counting
|
||||
// entities and not rows in a table.
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_int',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'integer',
|
||||
'cardinality' => 2,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
$this->assertFalse($field_storage->hasData(), 'There are no entities with field data.');
|
||||
$this->assertSame(0, $this->storage->countFieldData($field_storage), 'There are 0 entities with field data.');
|
||||
|
||||
// Create 1 entity without the field.
|
||||
$entity = EntityTest::create();
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
$this->assertFalse($field_storage->hasData(), 'There are no entities with field data.');
|
||||
$this->assertSame(0, $this->storage->countFieldData($field_storage), 'There are 0 entities with field data.');
|
||||
|
||||
// Create 12 entities to ensure that the purging works as expected.
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$entity = EntityTest::create();
|
||||
$entity->field_int[] = mt_rand(1, 99);
|
||||
$entity->field_int[] = mt_rand(1, 99);
|
||||
$entity->name[] = $this->randomMachineName();
|
||||
$entity->save();
|
||||
}
|
||||
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('entity_test');
|
||||
if ($storage instanceof SqlContentEntityStorage) {
|
||||
// Count the actual number of rows in the field table.
|
||||
$table_mapping = $storage->getTableMapping();
|
||||
$field_table_name = $table_mapping->getDedicatedDataTableName($field_storage);
|
||||
$result = Database::getConnection()->select($field_table_name, 't')
|
||||
->fields('t')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals(24, $result, 'The field table has 24 rows.');
|
||||
}
|
||||
|
||||
$this->assertTrue($field_storage->hasData(), 'There are entities with field data.');
|
||||
$this->assertEquals(12, $this->storage->countFieldData($field_storage), 'There are 12 entities with field data.');
|
||||
|
||||
// Ensure the methods work on deleted fields.
|
||||
$field_storage->delete();
|
||||
$this->assertTrue($field_storage->hasData(), 'There are entities with deleted field data.');
|
||||
$this->assertEquals(12, $this->storage->countFieldData($field_storage), 'There are 12 entities with deleted field data.');
|
||||
|
||||
field_purge_batch(6);
|
||||
$this->assertTrue($field_storage->hasData(), 'There are entities with deleted field data.');
|
||||
$this->assertEquals(6, $this->storage->countFieldData($field_storage), 'There are 6 entities with deleted field data.');
|
||||
|
||||
$entity_type = 'entity_test_rev';
|
||||
$this->createFieldWithStorage('_2', $entity_type);
|
||||
|
||||
$entity_init = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity_type)
|
||||
->create(['type' => $entity_type]);
|
||||
$cardinality = $this->fieldTestData->field_storage_2->getCardinality();
|
||||
|
||||
$this->assertFalse($this->fieldTestData->field_storage_2->hasData(), 'There are no entities with field data.');
|
||||
$this->assertSame(0, $this->storageRev->countFieldData($this->fieldTestData->field_storage_2), 'There are 0 entities with field data.');
|
||||
|
||||
// Create 1 entity with the field.
|
||||
$entity = clone($entity_init);
|
||||
$values = $this->_generateTestFieldValues($this->fieldTestData->field_storage_2->getCardinality());
|
||||
$entity->{$this->fieldTestData->field_name_2} = $values;
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$first_revision = $entity->getRevisionId();
|
||||
|
||||
$this->assertTrue($this->fieldTestData->field_storage_2->hasData(), 'There are entities with field data.');
|
||||
$this->assertSame(1, $this->storageRev->countFieldData($this->fieldTestData->field_storage_2), 'There is 1 entity with field data.');
|
||||
|
||||
$entity->{$this->fieldTestData->field_name_2} = [];
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->assertTrue($this->fieldTestData->field_storage_2->hasData(), 'There are entities with field data.');
|
||||
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage($entity_type);
|
||||
$entity = $storage->loadRevision($first_revision);
|
||||
$this->assertCount($cardinality, $entity->{$this->fieldTestData->field_name_2}, "Revision $first_revision: expected number of values.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that we can count a table that contains an entry with index 0.
|
||||
*/
|
||||
public function testCountWithIndex0(): void {
|
||||
// Create a field that will require dedicated storage.
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_int',
|
||||
'entity_type' => 'user',
|
||||
'type' => 'integer',
|
||||
'cardinality' => 2,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'user',
|
||||
])->save();
|
||||
|
||||
// Create an entry for the anonymous user, who has user ID 0.
|
||||
$user = $this->storageUser
|
||||
->create([
|
||||
'uid' => 0,
|
||||
'name' => 'anonymous',
|
||||
'mail' => NULL,
|
||||
'status' => FALSE,
|
||||
'field_int' => 42,
|
||||
]);
|
||||
$user->save();
|
||||
|
||||
// Test shared table storage.
|
||||
$storage = $user->getFieldDefinition('name')->getFieldStorageDefinition();
|
||||
$this->assertTrue($this->storageUser->countFieldData($storage, TRUE));
|
||||
|
||||
// Test dedicated table storage.
|
||||
$storage = $user->getFieldDefinition('field_int')->getFieldStorageDefinition();
|
||||
$this->assertTrue($this->storageUser->countFieldData($storage, TRUE));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Entity\ContentEntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Extension\Extension;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the integrity of field API plugin definitions.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldDefinitionIntegrityTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['system', 'path_alias'];
|
||||
|
||||
/**
|
||||
* Tests the integrity of field plugin definitions.
|
||||
*/
|
||||
public function testFieldPluginDefinitionIntegrity(): void {
|
||||
// Enable all core modules that provide field plugins, and their
|
||||
// dependencies.
|
||||
$this->enableModules(
|
||||
$this->modulesWithSubdirectory(
|
||||
'src' . DIRECTORY_SEPARATOR . 'Plugin' . DIRECTORY_SEPARATOR . 'Field'
|
||||
)
|
||||
);
|
||||
|
||||
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
|
||||
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
|
||||
$field_formatter_manager = \Drupal::service('plugin.manager.field.formatter');
|
||||
|
||||
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
|
||||
$field_widget_manager = \Drupal::service('plugin.manager.field.widget');
|
||||
|
||||
// Load the IDs of all available field type plugins.
|
||||
$available_field_type_ids = [];
|
||||
foreach ($field_type_manager->getDefinitions() as $definition) {
|
||||
$available_field_type_ids[] = $definition['id'];
|
||||
}
|
||||
|
||||
// Load the IDs of all available field widget plugins.
|
||||
$available_field_widget_ids = [];
|
||||
foreach ($field_widget_manager->getDefinitions() as $definition) {
|
||||
$available_field_widget_ids[] = $definition['id'];
|
||||
}
|
||||
|
||||
// Load the IDs of all available field formatter plugins.
|
||||
$available_field_formatter_ids = [];
|
||||
foreach ($field_formatter_manager->getDefinitions() as $definition) {
|
||||
$available_field_formatter_ids[] = $definition['id'];
|
||||
}
|
||||
|
||||
// Test the field type plugins.
|
||||
foreach ($field_type_manager->getDefinitions() as $definition) {
|
||||
// Test default field widgets.
|
||||
if (isset($definition['default_widget'])) {
|
||||
$this->assertContains($definition['default_widget'], $available_field_widget_ids, sprintf('Field type %s uses a non-existent field widget by default: %s', $definition['id'], $definition['default_widget']));
|
||||
}
|
||||
|
||||
// Test default field formatters.
|
||||
if (isset($definition['default_formatter'])) {
|
||||
$this->assertContains($definition['default_formatter'], $available_field_formatter_ids, sprintf('Field type %s uses a non-existent field formatter by default: %s', $definition['id'], $definition['default_formatter']));
|
||||
}
|
||||
}
|
||||
|
||||
// Test the field widget plugins.
|
||||
foreach ($field_widget_manager->getDefinitions() as $definition) {
|
||||
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
|
||||
$this->assertEmpty($missing_field_type_ids, sprintf('Field widget %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
|
||||
}
|
||||
|
||||
// Test the field formatter plugins.
|
||||
foreach ($field_formatter_manager->getDefinitions() as $definition) {
|
||||
$missing_field_type_ids = array_diff($definition['field_types'], $available_field_type_ids);
|
||||
$this->assertEmpty($missing_field_type_ids, sprintf('Field formatter %s integrates with non-existent field types: %s', $definition['id'], implode(', ', $missing_field_type_ids)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to load field plugin definitions used in core's existing entities.
|
||||
*/
|
||||
public function testFieldPluginDefinitionAvailability(): void {
|
||||
$this->enableModules(
|
||||
$this->modulesWithSubdirectory('src' . DIRECTORY_SEPARATOR . 'Entity')
|
||||
);
|
||||
|
||||
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
|
||||
$field_formatter_manager = $this->container->get('plugin.manager.field.formatter');
|
||||
|
||||
/** @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface $field_type_manager */
|
||||
$field_widget_manager = $this->container->get('plugin.manager.field.widget');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
|
||||
$entity_field_manager = $this->container->get('entity_field.manager');
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
|
||||
$entity_type_manager = $this->container->get('entity_type.manager');
|
||||
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[][] $field_definitions */
|
||||
$field_definitions = [];
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $content_entity_types */
|
||||
$content_entity_types = array_filter($entity_type_manager->getDefinitions(), function (EntityTypeInterface $entity_type) {
|
||||
return $entity_type instanceof ContentEntityTypeInterface;
|
||||
});
|
||||
|
||||
foreach ($content_entity_types as $entity_type_id => $entity_type_definition) {
|
||||
$field_definitions[$entity_type_id] = $entity_field_manager->getBaseFieldDefinitions($entity_type_id);
|
||||
}
|
||||
|
||||
foreach ($field_definitions as $entity_type_id => $definitions) {
|
||||
foreach ($definitions as $field_id => $field_definition) {
|
||||
$this->checkDisplayOption($entity_type_id, $field_id, $field_definition, $field_formatter_manager, 'view');
|
||||
$this->checkDisplayOption($entity_type_id, $field_id, $field_definition, $field_widget_manager, 'form');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that tries to load plugin definitions.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* Id of entity type. Required by message.
|
||||
* @param string $field_id
|
||||
* Id of field. Required by message.
|
||||
* @param \Drupal\Core\Field\BaseFieldDefinition $field_definition
|
||||
* Field definition that provide display options.
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $plugin_manager
|
||||
* Plugin manager that will try to provide plugin definition.
|
||||
* @param string $display_context
|
||||
* Defines which display options should be loaded.
|
||||
*/
|
||||
protected function checkDisplayOption($entity_type_id, $field_id, BaseFieldDefinition $field_definition, DiscoveryInterface $plugin_manager, $display_context): void {
|
||||
$display_options = $field_definition->getDisplayOptions($display_context);
|
||||
if (!empty($display_options['type'])) {
|
||||
$plugin = $plugin_manager->getDefinition($display_options['type'], FALSE);
|
||||
$this->assertNotNull($plugin, sprintf(
|
||||
'Plugin found for "%s" field %s display options of "%s" entity type.',
|
||||
$field_id,
|
||||
$display_context,
|
||||
$entity_type_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find modules with a specified subdirectory.
|
||||
*
|
||||
* @param string $subdirectory
|
||||
* The required path, relative to the module directory.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of module names satisfying these criteria:
|
||||
* - provided by core
|
||||
* - not hidden
|
||||
* - not already enabled
|
||||
* - not in the Testing package
|
||||
* - containing the required $subdirectory
|
||||
* and all modules required by any of these modules.
|
||||
*/
|
||||
protected function modulesWithSubdirectory($subdirectory): array {
|
||||
$modules = \Drupal::service('extension.list.module')->getList();
|
||||
$modules = array_filter($modules, function (Extension $module) use ($subdirectory) {
|
||||
// Filter contrib, hidden, already enabled modules and modules in the
|
||||
// Testing package.
|
||||
return ($module->origin === 'core'
|
||||
&& empty($module->info['hidden'])
|
||||
&& $module->status == FALSE
|
||||
&& $module->info['package'] !== 'Testing'
|
||||
&& is_readable($module->getPath() . DIRECTORY_SEPARATOR . $subdirectory));
|
||||
});
|
||||
// Gather the dependencies of the modules.
|
||||
$dependencies = NestedArray::mergeDeepArray(array_map(function (Extension $module) {
|
||||
return array_keys($module->requires);
|
||||
}, $modules));
|
||||
|
||||
return array_unique(NestedArray::mergeDeep(array_keys($modules), $dependencies));
|
||||
}
|
||||
|
||||
}
|
||||
126
web/core/modules/field/tests/src/Kernel/FieldDisplayTest.php
Normal file
126
web/core/modules/field/tests/src/Kernel/FieldDisplayTest.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Symfony\Component\CssSelector\CssSelectorConverter;
|
||||
|
||||
/**
|
||||
* Tests Field display.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldDisplayTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'entity_test',
|
||||
'field',
|
||||
'system',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test entity type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* Test entity bundle name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* Test field name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* Entity view display.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Configure the theme system.
|
||||
$this->installConfig(['system', 'field']);
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
|
||||
$this->entityType = 'entity_test_rev';
|
||||
$this->bundle = $this->entityType;
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityType,
|
||||
'type' => 'string',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$values = [
|
||||
'targetEntityType' => $this->entityType,
|
||||
'bundle' => $this->bundle,
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
];
|
||||
|
||||
$this->display = \Drupal::entityTypeManager()
|
||||
->getStorage('entity_view_display')
|
||||
->create($values);
|
||||
$this->display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that visually hidden works with core.
|
||||
*/
|
||||
public function testFieldVisualHidden(): void {
|
||||
$value = $this->randomMachineName();
|
||||
|
||||
// Set the formatter to link to the entity.
|
||||
$this->display->setComponent($this->fieldName, [
|
||||
'type' => 'string',
|
||||
'label' => 'visually_hidden',
|
||||
'settings' => [],
|
||||
])->save();
|
||||
|
||||
$entity = EntityTestRev::create([]);
|
||||
$entity->{$this->fieldName}->value = $value;
|
||||
$entity->save();
|
||||
|
||||
$build = $this->display->build($entity);
|
||||
$renderer = \Drupal::service('renderer');
|
||||
$content = (string) $renderer->renderInIsolation($build);
|
||||
$this->setRawContent($content);
|
||||
|
||||
$css_selector_converter = new CssSelectorConverter();
|
||||
$elements = $this->xpath($css_selector_converter->toXPath('.visually-hidden'));
|
||||
$this->assertCount(1, $elements, $content);
|
||||
}
|
||||
|
||||
}
|
||||
106
web/core/modules/field/tests/src/Kernel/FieldFormatterTest.php
Normal file
106
web/core/modules/field/tests/src/Kernel/FieldFormatterTest.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests for field formatters.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldFormatterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'text',
|
||||
'entity_test',
|
||||
'field_test',
|
||||
'system',
|
||||
'filter',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* The field's name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* The default display.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Configure the theme system.
|
||||
$this->installConfig(['system', 'field']);
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
|
||||
$entity_type = 'entity_test_rev';
|
||||
$bundle = $entity_type;
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'string',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$this->display = EntityViewDisplay::create([
|
||||
'targetEntityType' => $entity_type,
|
||||
'bundle' => $bundle,
|
||||
'mode' => 'default',
|
||||
'status' => TRUE,
|
||||
])->setComponent($this->fieldName, [
|
||||
'type' => 'string',
|
||||
'settings' => [],
|
||||
]);
|
||||
$this->display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests availability of third party settings in field render arrays.
|
||||
*/
|
||||
public function testThirdPartySettings(): void {
|
||||
$third_party_settings = [
|
||||
'field_test' => [
|
||||
'foo' => 'bar',
|
||||
],
|
||||
];
|
||||
$component = $this->display->getComponent($this->fieldName);
|
||||
$component['third_party_settings'] = $third_party_settings;
|
||||
$this->display->setComponent($this->fieldName, $component)->save();
|
||||
$entity = EntityTestRev::create([]);
|
||||
|
||||
$entity->{$this->fieldName}->value = $this->randomString();
|
||||
$build = $entity->{$this->fieldName}->view('default');
|
||||
$this->assertEquals($third_party_settings, $build['#third_party_settings']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
|
||||
/**
|
||||
* Update field storage and fields during config change method invocation.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldImportChangeTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* The default configuration provided by field_test_config is imported by
|
||||
* \Drupal\Tests\field\Kernel\FieldKernelTestBase::setUp() when it installs
|
||||
* field configuration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['field_test_config'];
|
||||
|
||||
/**
|
||||
* Tests importing an updated field.
|
||||
*/
|
||||
public function testImportChange(): void {
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
$this->installConfig(['field_test_config']);
|
||||
$field_storage_id = 'field_test_import';
|
||||
$field_id = "entity_test.entity_test.$field_storage_id";
|
||||
$field_config_name = "field.field.$field_id";
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Save as files in the sync directory.
|
||||
$field = $active->read($field_config_name);
|
||||
$new_label = 'Test update import field';
|
||||
$field['label'] = $new_label;
|
||||
$sync->write($field_config_name, $field);
|
||||
|
||||
// Import the content of the sync directory.
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the updated config was correctly imported.
|
||||
$field = FieldConfig::load($field_id);
|
||||
$this->assertEquals($new_label, $field->getLabel(), 'field label updated');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,126 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Site\Settings;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Create field storages and fields during config create method invocation.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldImportCreateTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests creating field storages and fields during default config import.
|
||||
*/
|
||||
public function testImportCreateDefault(): void {
|
||||
$field_name = 'field_test_import';
|
||||
$field_storage_id = "entity_test.$field_name";
|
||||
$field_id = "entity_test.entity_test.$field_name";
|
||||
$field_name_2 = 'field_test_import_2';
|
||||
$field_storage_id_2 = "entity_test.$field_name_2";
|
||||
$field_id_2a = "entity_test.entity_test.$field_name_2";
|
||||
$field_id_2b = "entity_test.test_bundle.$field_name_2";
|
||||
|
||||
// Check that the field storages and fields do not exist yet.
|
||||
$this->assertNull(FieldStorageConfig::load($field_storage_id));
|
||||
$this->assertNull(FieldConfig::load($field_id));
|
||||
$this->assertNull(FieldStorageConfig::load($field_storage_id_2));
|
||||
$this->assertNull(FieldConfig::load($field_id_2a));
|
||||
$this->assertNull(FieldConfig::load($field_id_2b));
|
||||
|
||||
// Create a second bundle for the 'Entity test' entity type.
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
// Enable field_test_config module and check that the field and storage
|
||||
// shipped in the module's default config were created.
|
||||
\Drupal::service('module_installer')->install(['field_test_config']);
|
||||
|
||||
// A field storage with one single field.
|
||||
$field_storage = FieldStorageConfig::load($field_storage_id);
|
||||
$this->assertNotEmpty($field_storage, 'The field was created.');
|
||||
$field = FieldConfig::load($field_id);
|
||||
$this->assertNotEmpty($field, 'The field was deleted.');
|
||||
|
||||
// A field storage with two fields.
|
||||
$field_storage_2 = FieldStorageConfig::load($field_storage_id_2);
|
||||
$this->assertNotEmpty($field_storage_2, 'The second field was created.');
|
||||
$field2a = FieldConfig::load($field_id_2a);
|
||||
$this->assertEquals('entity_test', $field2a->getTargetBundle(), 'The second field was created on bundle entity_test.');
|
||||
$field2b = FieldConfig::load($field_id_2b);
|
||||
$this->assertEquals('test_bundle', $field2b->getTargetBundle(), 'The second field was created on bundle test_bundle.');
|
||||
|
||||
// Tests fields.
|
||||
$ids = \Drupal::entityQuery('field_config')
|
||||
->condition('entity_type', 'entity_test')
|
||||
->condition('bundle', 'entity_test')
|
||||
->execute();
|
||||
$this->assertCount(2, $ids);
|
||||
$this->assertTrue(isset($ids['entity_test.entity_test.field_test_import']));
|
||||
$this->assertTrue(isset($ids['entity_test.entity_test.field_test_import_2']));
|
||||
$ids = \Drupal::entityQuery('field_config')
|
||||
->condition('entity_type', 'entity_test')
|
||||
->condition('bundle', 'test_bundle')
|
||||
->execute();
|
||||
$this->assertCount(1, $ids);
|
||||
$this->assertTrue(isset($ids['entity_test.test_bundle.field_test_import_2']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating field storages and fields during config import.
|
||||
*/
|
||||
public function testImportCreate(): void {
|
||||
// A field storage with one single field.
|
||||
$field_name = 'field_test_import_sync';
|
||||
$field_storage_id = "entity_test.$field_name";
|
||||
$field_id = "entity_test.entity_test.$field_name";
|
||||
$field_storage_config_name = "field.storage.$field_storage_id";
|
||||
$field_config_name = "field.field.$field_id";
|
||||
|
||||
// A field storage with two fields.
|
||||
$field_name_2 = 'field_test_import_sync_2';
|
||||
$field_storage_id_2 = "entity_test.$field_name_2";
|
||||
$field_id_2a = "entity_test.test_bundle.$field_name_2";
|
||||
$field_id_2b = "entity_test.test_bundle_2.$field_name_2";
|
||||
$field_storage_config_name_2 = "field.storage.$field_storage_id_2";
|
||||
$field_config_name_2a = "field.field.$field_id_2a";
|
||||
$field_config_name_2b = "field.field.$field_id_2b";
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Add the new files to the sync directory.
|
||||
$src_dir = __DIR__ . '/../../modules/field_test_config/sync';
|
||||
$target_dir = Settings::get('config_sync_directory');
|
||||
/** @var \Drupal\Core\File\FileSystemInterface $file_system */
|
||||
$file_system = \Drupal::service('file_system');
|
||||
$this->assertNotFalse($file_system->copy("$src_dir/$field_storage_config_name.yml", "$target_dir/$field_storage_config_name.yml"));
|
||||
$this->assertNotFalse($file_system->copy("$src_dir/$field_config_name.yml", "$target_dir/$field_config_name.yml"));
|
||||
$this->assertNotFalse($file_system->copy("$src_dir/$field_storage_config_name_2.yml", "$target_dir/$field_storage_config_name_2.yml"));
|
||||
$this->assertNotFalse($file_system->copy("$src_dir/$field_config_name_2a.yml", "$target_dir/$field_config_name_2a.yml"));
|
||||
$this->assertNotFalse($file_system->copy("$src_dir/$field_config_name_2b.yml", "$target_dir/$field_config_name_2b.yml"));
|
||||
|
||||
// Import the content of the sync directory.
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the field and storage were created.
|
||||
$field_storage = FieldStorageConfig::load($field_storage_id);
|
||||
$this->assertNotEmpty($field_storage, 'Test import storage field from sync exists');
|
||||
$field = FieldConfig::load($field_id);
|
||||
$this->assertNotEmpty($field, 'Test import field from sync exists');
|
||||
$field_storage = FieldStorageConfig::load($field_storage_id_2);
|
||||
$this->assertNotEmpty($field_storage, 'Test import storage field 2 from sync exists');
|
||||
$field = FieldConfig::load($field_id_2a);
|
||||
$this->assertNotEmpty($field, 'Test import field 2a from sync exists');
|
||||
$field = FieldConfig::load($field_id_2b);
|
||||
$this->assertNotEmpty($field, 'Test import field 2b from sync exists');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Delete field storages and fields during config delete method invocation.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldImportDeleteTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* The default configuration provided by field_test_config is imported by
|
||||
* \Drupal\Tests\field\Kernel\FieldKernelTestBase::setUp() when it installs
|
||||
* field configuration.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['field_test_config'];
|
||||
|
||||
/**
|
||||
* Tests deleting field storages and fields as part of config import.
|
||||
*/
|
||||
public function testImportDelete(): void {
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
$this->installConfig(['field_test_config']);
|
||||
// At this point there are 5 field configuration objects in the active
|
||||
// storage.
|
||||
// - field.storage.entity_test.field_test_import
|
||||
// - field.storage.entity_test.field_test_import_2
|
||||
// - field.field.entity_test.entity_test.field_test_import
|
||||
// - field.field.entity_test.entity_test.field_test_import_2
|
||||
// - field.field.entity_test.test_bundle.field_test_import_2
|
||||
|
||||
$field_name = 'field_test_import';
|
||||
$field_storage_id = "entity_test.$field_name";
|
||||
$field_name_2 = 'field_test_import_2';
|
||||
$field_storage_id_2 = "entity_test.$field_name_2";
|
||||
$field_id = "entity_test.entity_test.$field_name";
|
||||
$field_id_2a = "entity_test.entity_test.$field_name_2";
|
||||
$field_id_2b = "entity_test.test_bundle.$field_name_2";
|
||||
$field_storage_config_name = "field.storage.$field_storage_id";
|
||||
$field_storage_config_name_2 = "field.storage.$field_storage_id_2";
|
||||
$field_config_name = "field.field.$field_id";
|
||||
$field_config_name_2a = "field.field.$field_id_2a";
|
||||
$field_config_name_2b = "field.field.$field_id_2b";
|
||||
|
||||
// Create an entity with data in the first field to make sure that field
|
||||
// needs to be purged.
|
||||
$entity_test = EntityTest::create([
|
||||
'type' => 'entity_test',
|
||||
]);
|
||||
$entity_test->set($field_name, 'test data');
|
||||
$entity_test->save();
|
||||
|
||||
// Create a second bundle for the 'Entity test' entity type.
|
||||
EntityTestHelper::createBundle('test_bundle');
|
||||
|
||||
// Get the uuid's for the field storages.
|
||||
$field_storage_uuid = FieldStorageConfig::load($field_storage_id)->uuid();
|
||||
$field_storage_uuid_2 = FieldStorageConfig::load($field_storage_id_2)->uuid();
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
$this->assertTrue($sync->delete($field_storage_config_name), "Deleted field storage: $field_storage_config_name");
|
||||
$this->assertTrue($sync->delete($field_storage_config_name_2), "Deleted field storage: $field_storage_config_name_2");
|
||||
$this->assertTrue($sync->delete($field_config_name), "Deleted field: $field_config_name");
|
||||
$this->assertTrue($sync->delete($field_config_name_2a), "Deleted field: $field_config_name_2a");
|
||||
$this->assertTrue($sync->delete($field_config_name_2b), "Deleted field: $field_config_name_2b");
|
||||
|
||||
$deletes = $this->configImporter()->getUnprocessedConfiguration('delete');
|
||||
$this->assertCount(5, $deletes, 'Importing configuration will delete 3 fields and 2 field storages.');
|
||||
|
||||
// Import the content of the sync directory.
|
||||
$this->configImporter()->import();
|
||||
|
||||
// Check that the field storages and fields are gone.
|
||||
\Drupal::entityTypeManager()->getStorage('field_storage_config')->resetCache([$field_storage_id]);
|
||||
$field_storage = FieldStorageConfig::load($field_storage_id);
|
||||
$this->assertNull($field_storage, 'The field storage was deleted.');
|
||||
\Drupal::entityTypeManager()->getStorage('field_storage_config')->resetCache([$field_storage_id_2]);
|
||||
$field_storage_2 = FieldStorageConfig::load($field_storage_id_2);
|
||||
$this->assertNull($field_storage_2, 'The second field storage was deleted.');
|
||||
\Drupal::entityTypeManager()->getStorage('field_config')->resetCache([$field_id]);
|
||||
$field = FieldConfig::load($field_id);
|
||||
$this->assertNull($field, 'The field was deleted.');
|
||||
\Drupal::entityTypeManager()->getStorage('field_config')->resetCache([$field_id_2a]);
|
||||
$field_2a = FieldConfig::load($field_id_2a);
|
||||
$this->assertNull($field_2a, 'The second field on test bundle was deleted.');
|
||||
\Drupal::entityTypeManager()->getStorage('field_config')->resetCache([$field_id_2b]);
|
||||
$field_2b = FieldConfig::load($field_id_2b);
|
||||
$this->assertNull($field_2b, 'The second field on test bundle 2 was deleted.');
|
||||
|
||||
// Check that all config files are gone.
|
||||
$active = $this->container->get('config.storage');
|
||||
$this->assertSame([], $active->listAll($field_storage_config_name));
|
||||
$this->assertSame([], $active->listAll($field_storage_config_name_2));
|
||||
$this->assertSame([], $active->listAll($field_config_name));
|
||||
$this->assertSame([], $active->listAll($field_config_name_2a));
|
||||
$this->assertSame([], $active->listAll($field_config_name_2b));
|
||||
|
||||
// Check that only the first storage definition is preserved in state.
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertTrue(isset($deleted_storages[$field_storage_uuid]));
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage_uuid_2]));
|
||||
|
||||
// Purge field data, and check that the storage definition has been
|
||||
// completely removed once the data is purged.
|
||||
field_purge_batch(10);
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertEmpty($deleted_storages, 'Fields are deleted');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests field storages and fields deletion during config synchronization.
|
||||
*
|
||||
* @group field
|
||||
* @see \Drupal\field\ConfigImporterFieldPurger
|
||||
* @see field_config_import_steps_alter()
|
||||
*/
|
||||
class FieldImportDeleteUninstallTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['telephone'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Module uninstall requires users_data tables.
|
||||
// @see drupal_flush_all_caches()
|
||||
// @see user_modules_uninstalled()
|
||||
$this->installSchema('user', ['users_data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting field storages and fields as part of config import.
|
||||
*/
|
||||
public function testImportDeleteUninstall(): void {
|
||||
// Create a field to delete to prove that
|
||||
// \Drupal\field\ConfigImporterFieldPurger does not purge fields that are
|
||||
// not related to the configuration synchronization.
|
||||
$unrelated_field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_int',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'integer',
|
||||
]);
|
||||
$unrelated_field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $unrelated_field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a telephone field for validation.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
$entity = EntityTest::create();
|
||||
$value = '+0123456789';
|
||||
$entity->field_test = $value;
|
||||
$entity->field_int = '99';
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->field_test->value);
|
||||
$this->assertEquals($value, $entity->field_test[0]->value);
|
||||
$this->assertEquals('99', $entity->field_int->value);
|
||||
|
||||
// Delete unrelated field before copying configuration and running the
|
||||
// synchronization.
|
||||
$unrelated_field_storage->delete();
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Stage uninstall of the Telephone module.
|
||||
$core_extension = $this->config('core.extension')->get();
|
||||
unset($core_extension['module']['telephone']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
// Stage the field deletion
|
||||
$sync->delete('field.storage.entity_test.field_test');
|
||||
$sync->delete('field.field.entity_test.entity_test.field_test');
|
||||
|
||||
$steps = $this->configImporter()->initialize();
|
||||
$this->assertSame(['\\Drupal\\field\\ConfigImporterFieldPurger', 'process'], $steps[0], 'The additional process configuration synchronization step has been added.');
|
||||
|
||||
// This will purge all the data, delete the field and uninstall the
|
||||
// Telephone module.
|
||||
$this->configImporter()->import();
|
||||
|
||||
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
|
||||
$this->assertNull(\Drupal::service('entity.repository')->loadEntityByUuid('field_storage_config', $field_storage->uuid()), 'The test field has been deleted by the configuration synchronization');
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage->uuid()]), 'Telephone field has been completed removed from the system.');
|
||||
$this->assertTrue(isset($deleted_storages[$unrelated_field_storage->uuid()]), 'Unrelated field not purged by configuration synchronization.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests purging previously deleted fields and storages in config import.
|
||||
*/
|
||||
public function testImportAlreadyDeletedUninstall(): void {
|
||||
// Create a telephone field for validation.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'telephone',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field_storage_uuid = $field_storage->uuid();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create 12 entities to ensure that the purging works as expected.
|
||||
for ($i = 0; $i < 12; $i++) {
|
||||
$entity = EntityTest::create();
|
||||
$value = '+0123456789';
|
||||
$entity->field_test = $value;
|
||||
$entity->name->value = $this->randomMachineName();
|
||||
$entity->save();
|
||||
|
||||
// Verify entity has been created properly.
|
||||
$id = $entity->id();
|
||||
$entity = EntityTest::load($id);
|
||||
$this->assertEquals($value, $entity->field_test->value);
|
||||
}
|
||||
|
||||
// Delete the field.
|
||||
$field_storage->delete();
|
||||
|
||||
$active = $this->container->get('config.storage');
|
||||
$sync = $this->container->get('config.storage.sync');
|
||||
$this->copyConfig($active, $sync);
|
||||
|
||||
// Stage uninstall of the Telephone module.
|
||||
$core_extension = $this->config('core.extension')->get();
|
||||
unset($core_extension['module']['telephone']);
|
||||
$sync->write('core.extension', $core_extension);
|
||||
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertTrue(isset($deleted_storages[$field_storage_uuid]), 'Field has been deleted and needs purging before configuration synchronization.');
|
||||
|
||||
$steps = $this->configImporter()->initialize();
|
||||
$this->assertSame(['\\Drupal\\field\\ConfigImporterFieldPurger', 'process'], $steps[0], 'The additional process configuration synchronization step has been added.');
|
||||
|
||||
// This will purge all the data, delete the field and uninstall the
|
||||
// Telephone module.
|
||||
$this->configImporter()->import();
|
||||
|
||||
$this->assertFalse(\Drupal::moduleHandler()->moduleExists('telephone'));
|
||||
$deleted_storages = \Drupal::state()->get('field.storage.deleted', []);
|
||||
$this->assertFalse(isset($deleted_storages[$field_storage_uuid]), 'Field has been completed removed from the system.');
|
||||
}
|
||||
|
||||
}
|
||||
218
web/core/modules/field/tests/src/Kernel/FieldKernelTestBase.php
Normal file
218
web/core/modules/field/tests/src/Kernel/FieldKernelTestBase.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Parent class for Field API unit tests.
|
||||
*/
|
||||
abstract class FieldKernelTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'user',
|
||||
'system',
|
||||
'field',
|
||||
'text',
|
||||
'entity_test',
|
||||
'field_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* Bag of created field storages and fields.
|
||||
*
|
||||
* Allows easy access to test field storage/field names/IDs/objects via:
|
||||
* - $this->fieldTestData->field_name[suffix]
|
||||
* - $this->fieldTestData->field_storage[suffix]
|
||||
* - $this->fieldTestData->field_storage_uuid[suffix]
|
||||
* - $this->fieldTestData->field[suffix]
|
||||
* - $this->fieldTestData->field_definition[suffix]
|
||||
*
|
||||
* @var \ArrayObject
|
||||
*
|
||||
* @see \Drupal\field\Tests\FieldUnitTestBase::createFieldWithStorage()
|
||||
*/
|
||||
protected $fieldTestData;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $entityId;
|
||||
|
||||
/**
|
||||
* Set the default field storage backend for fields created during tests.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->fieldTestData = new \ArrayObject([], \ArrayObject::ARRAY_AS_PROPS);
|
||||
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('user');
|
||||
|
||||
// Set default storage backend and configure the theme system.
|
||||
$this->installConfig(['field', 'system', 'user']);
|
||||
|
||||
// Create user 1.
|
||||
$storage = \Drupal::entityTypeManager()->getStorage('user');
|
||||
$storage
|
||||
->create([
|
||||
'uid' => 1,
|
||||
'name' => 'entity-test',
|
||||
'mail' => 'entity@localhost',
|
||||
'status' => TRUE,
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a field and an associated field storage.
|
||||
*
|
||||
* @param string $suffix
|
||||
* (optional) A string that should only contain characters that are valid in
|
||||
* PHP variable names as well.
|
||||
* @param string $entity_type
|
||||
* (optional) The entity type on which the field should be created.
|
||||
* Defaults to "entity_test".
|
||||
* @param string $bundle
|
||||
* (optional) The entity type on which the field should be created.
|
||||
* Defaults to the default bundle of the entity type.
|
||||
*/
|
||||
protected function createFieldWithStorage($suffix = '', $entity_type = 'entity_test', $bundle = NULL) {
|
||||
if (empty($bundle)) {
|
||||
$bundle = $entity_type;
|
||||
}
|
||||
$field_name = 'field_name' . $suffix;
|
||||
$field_storage = 'field_storage' . $suffix;
|
||||
$field_storage_uuid = 'field_storage_uuid' . $suffix;
|
||||
$field = 'field' . $suffix;
|
||||
$field_definition = 'field_definition' . $suffix;
|
||||
|
||||
$this->fieldTestData->$field_name = $this->randomMachineName() . '_field_name' . $suffix;
|
||||
$this->fieldTestData->$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldTestData->$field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 4,
|
||||
]);
|
||||
$this->fieldTestData->$field_storage->save();
|
||||
$this->fieldTestData->$field_storage_uuid = $this->fieldTestData->$field_storage->uuid();
|
||||
$this->fieldTestData->$field_definition = [
|
||||
'field_storage' => $this->fieldTestData->$field_storage,
|
||||
'bundle' => $bundle,
|
||||
'label' => $this->randomMachineName() . '_label',
|
||||
'description' => $this->randomMachineName() . '_description',
|
||||
'settings' => [
|
||||
'test_field_setting' => $this->randomMachineName(),
|
||||
],
|
||||
];
|
||||
$this->fieldTestData->$field = FieldConfig::create($this->fieldTestData->$field_definition);
|
||||
$this->fieldTestData->$field->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $bundle)
|
||||
->setComponent($this->fieldTestData->$field_name, [
|
||||
'type' => 'test_field_widget',
|
||||
'settings' => [
|
||||
'test_widget_setting' => $this->randomMachineName(),
|
||||
],
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves and reloads an entity.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to save.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface
|
||||
* The entity, freshly reloaded from storage.
|
||||
*/
|
||||
protected function entitySaveReload(EntityInterface $entity): EntityInterface {
|
||||
$entity->save();
|
||||
$controller = $this->container->get('entity_type.manager')->getStorage($entity->getEntityTypeId());
|
||||
$controller->resetCache();
|
||||
return $controller->load($entity->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and save entity. Fail if violations are found.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to save.
|
||||
*/
|
||||
protected function entityValidateAndSave(EntityInterface $entity) {
|
||||
$violations = $entity->validate();
|
||||
if ($violations->count()) {
|
||||
$this->fail((string) $violations);
|
||||
}
|
||||
else {
|
||||
$entity->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random values for a field_test field.
|
||||
*
|
||||
* @param int $cardinality
|
||||
* Number of values to generate.
|
||||
*
|
||||
* @return array
|
||||
* An array of random values, in the format expected for field values.
|
||||
*/
|
||||
protected function _generateTestFieldValues($cardinality) {
|
||||
$values = [];
|
||||
for ($i = 0; $i < $cardinality; $i++) {
|
||||
// field_test fields treat 0 as 'empty value'.
|
||||
$values[$i]['value'] = mt_rand(1, 127);
|
||||
}
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that a field has the expected values in an entity.
|
||||
*
|
||||
* This function only checks a single column in the field values.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* The entity to test.
|
||||
* @param string $field_name
|
||||
* The name of the field to test.
|
||||
* @param array $expected_values
|
||||
* The array of expected values.
|
||||
* @param string $langcode
|
||||
* (Optional) The language code for the values. Defaults to
|
||||
* \Drupal\Core\Language\LanguageInterface::LANGCODE_NOT_SPECIFIED.
|
||||
* @param string $column
|
||||
* (Optional) The name of the column to check. Defaults to 'value'.
|
||||
*/
|
||||
protected function assertFieldValues(EntityInterface $entity, $field_name, $expected_values, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $column = 'value') {
|
||||
$expected_values_count = count($expected_values);
|
||||
|
||||
// Re-load the entity to make sure we have the latest changes.
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($entity->getEntityTypeId());
|
||||
$storage->resetCache([$entity->id()]);
|
||||
$e = $storage->load($this->entityId);
|
||||
|
||||
$field = $values = $e->getTranslation($langcode)->$field_name;
|
||||
// Filter out empty values so that they don't mess with the assertions.
|
||||
$field->filterEmptyItems();
|
||||
$values = $field->getValue();
|
||||
$this->assertCount($expected_values_count, $values, 'Expected number of values were saved.');
|
||||
foreach ($expected_values as $key => $value) {
|
||||
$this->assertEquals($value, $values[$key][$column], "Value $value was saved correctly.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
521
web/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php
Normal file
521
web/core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php
Normal file
@ -0,0 +1,521 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
|
||||
use Drupal\Core\Field\FieldException;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field_test\FieldTestHelper;
|
||||
|
||||
/**
|
||||
* Tests field storage create, read, update, and delete.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldStorageCrudTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [];
|
||||
|
||||
// @todo Test creation with
|
||||
// - a full fledged $field structure, check that all the values are there
|
||||
// - a minimal $field structure, check all default values are set
|
||||
// defer actual $field comparison to a helper function, used for the two cases above
|
||||
|
||||
/**
|
||||
* Tests the creation of a field storage.
|
||||
*/
|
||||
public function testCreate(): void {
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'field_2',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
FieldTestHelper::memorize();
|
||||
$field_storage = FieldStorageConfig::create($field_storage_definition);
|
||||
$field_storage->save();
|
||||
|
||||
$field_storage = FieldStorageConfig::load($field_storage->id());
|
||||
$this->assertEquals('TRUE', $field_storage->getSetting('storage_setting_from_config_data'));
|
||||
$this->assertNull($field_storage->getSetting('config_data_from_storage_setting'));
|
||||
|
||||
$mem = FieldTestHelper::memorize();
|
||||
$this->assertSame($field_storage_definition['field_name'], $mem['Drupal\field_test\Hook\FieldTestHooks::fieldStorageConfigCreate'][0][0]->getName(), 'hook_entity_create() called with correct arguments.');
|
||||
$this->assertSame($field_storage_definition['type'], $mem['Drupal\field_test\Hook\FieldTestHooks::fieldStorageConfigCreate'][0][0]->getType(), 'hook_entity_create() called with correct arguments.');
|
||||
|
||||
// Read the configuration. Check against raw configuration data rather than
|
||||
// the loaded ConfigEntity, to be sure we check that the defaults are
|
||||
// applied on write.
|
||||
$field_storage_config = $this->config('field.storage.' . $field_storage->id())->get();
|
||||
|
||||
$this->assertTrue($field_storage_config['settings']['config_data_from_storage_setting']);
|
||||
$this->assertTrue(!isset($field_storage_config['settings']['storage_setting_from_config_data']));
|
||||
|
||||
// Since we are working with raw configuration, this needs to be unset
|
||||
// manually.
|
||||
// @see Drupal\field_test\Plugin\Field\FieldType\TestItem::storageSettingsFromConfigData()
|
||||
unset($field_storage_config['settings']['config_data_from_storage_setting']);
|
||||
|
||||
// Ensure that basic properties are preserved.
|
||||
$this->assertEquals($field_storage_definition['field_name'], $field_storage_config['field_name'], 'The field name is properly saved.');
|
||||
$this->assertEquals($field_storage_definition['entity_type'], $field_storage_config['entity_type'], 'The field entity type is properly saved.');
|
||||
$this->assertEquals($field_storage_definition['entity_type'] . '.' . $field_storage_definition['field_name'], $field_storage_config['id'], 'The field id is properly saved.');
|
||||
$this->assertEquals($field_storage_definition['type'], $field_storage_config['type'], 'The field type is properly saved.');
|
||||
|
||||
// Ensure that cardinality defaults to 1.
|
||||
$this->assertEquals(1, $field_storage_config['cardinality'], 'Cardinality defaults to 1.');
|
||||
|
||||
// Ensure that default settings are present.
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$this->assertEquals($field_type_manager->getDefaultStorageSettings($field_storage_definition['type']), $field_storage_config['settings'], 'Default storage settings have been written.');
|
||||
|
||||
// Guarantee that the name is unique.
|
||||
try {
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create two fields with the same name.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(EntityStorageException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field type is required.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'field_1',
|
||||
'entity_type' => 'entity_type',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field with no type.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field name is required.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'type' => 'test_field',
|
||||
'entity_type' => 'entity_test',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create an unnamed field.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
// Check that entity type is required.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'test_field',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field without an entity type.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field name must start with a letter or _.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'field_name' => '2field_2',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field with a name starting with a digit.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field name must only contain lowercase alphanumeric or _.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'field#_3',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field with a name containing an illegal character.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field name cannot be longer than 32 characters long.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'field_name' => '_12345678901234567890123456789012',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field with a name longer than 32 characters.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
|
||||
// Check that field name can not be an entity key.
|
||||
// "id" is known as an entity key from the "entity_test" type.
|
||||
try {
|
||||
$field_storage_definition = [
|
||||
'type' => 'test_field',
|
||||
'field_name' => 'id',
|
||||
'entity_type' => 'entity_test',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$this->fail('Cannot create a field bearing the name of an entity key.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an explicit schema can be provided on creation.
|
||||
*
|
||||
* This behavior is needed to allow field storage creation within updates,
|
||||
* since plugin classes (and thus the field type schema) cannot be accessed.
|
||||
*/
|
||||
public function testCreateWithExplicitSchema(): void {
|
||||
$schema = [
|
||||
'dummy' => 'foobar',
|
||||
];
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_2',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'schema' => $schema,
|
||||
]);
|
||||
$this->assertEquals($schema, $field_storage->getSchema());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests reading field storage definitions.
|
||||
*/
|
||||
public function testRead(): void {
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'field_1',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
];
|
||||
$field_storage = FieldStorageConfig::create($field_storage_definition);
|
||||
$field_storage->save();
|
||||
$id = $field_storage->id();
|
||||
|
||||
// Check that 'single column' criteria works.
|
||||
$field_storage_config_storage = \Drupal::entityTypeManager()->getStorage('field_storage_config');
|
||||
$fields = $field_storage_config_storage->loadByProperties(['field_name' => $field_storage_definition['field_name']]);
|
||||
$this->assertCount(1, $fields, 'The field was properly read.');
|
||||
$this->assertArrayHasKey($id, $fields, 'The field has the correct key.');
|
||||
|
||||
// Check that 'multi column' criteria works.
|
||||
$fields = $field_storage_config_storage->loadByProperties([
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'type' => $field_storage_definition['type'],
|
||||
'entity_type' => $field_storage_definition['entity_type'],
|
||||
]);
|
||||
$this->assertCount(1, $fields, 'The field was properly read.');
|
||||
$this->assertArrayHasKey($id, $fields, 'The field has the correct key.');
|
||||
$fields = $field_storage_config_storage->loadByProperties([
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'type' => 'foo',
|
||||
]);
|
||||
$this->assertEmpty($fields, 'No field was found.');
|
||||
|
||||
// Create a field from the field storage.
|
||||
$field_definition = [
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
];
|
||||
FieldConfig::create($field_definition)->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of indexes on data column.
|
||||
*/
|
||||
public function testIndexes(): void {
|
||||
// Check that indexes specified by the field type are used by default.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_1',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field_storage = FieldStorageConfig::load($field_storage->id());
|
||||
$schema = $field_storage->getSchema();
|
||||
$expected_indexes = ['value' => ['value']];
|
||||
$this->assertEquals($expected_indexes, $schema['indexes'], 'Field type indexes saved by default');
|
||||
|
||||
// Check that indexes specified by the field definition override the field
|
||||
// type indexes.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_2',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'indexes' => [
|
||||
'value' => [],
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field_storage = FieldStorageConfig::load($field_storage->id());
|
||||
$schema = $field_storage->getSchema();
|
||||
$expected_indexes = ['value' => []];
|
||||
$this->assertEquals($expected_indexes, $schema['indexes'], 'Field definition indexes override field type indexes');
|
||||
|
||||
// Check that indexes specified by the field definition add to the field
|
||||
// type indexes.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_3',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'indexes' => [
|
||||
'value_2' => ['value'],
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
$id = $field_storage->id();
|
||||
$field_storage = FieldStorageConfig::load($id);
|
||||
$schema = $field_storage->getSchema();
|
||||
$expected_indexes = ['value' => ['value'], 'value_2' => ['value']];
|
||||
$this->assertEquals($expected_indexes, $schema['indexes'], 'Field definition indexes are merged with field type indexes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deletion of a field storage.
|
||||
*/
|
||||
public function testDeleteNoData(): void {
|
||||
// Deleting and purging field storages with data is tested in
|
||||
// \Drupal\Tests\field\Kernel\BulkDeleteTest.
|
||||
|
||||
// Create two fields (so we can test that only one is deleted).
|
||||
$field_storage_definition = [
|
||||
'field_name' => 'field_1',
|
||||
'type' => 'test_field',
|
||||
'entity_type' => 'entity_test',
|
||||
];
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
$another_field_storage_definition = [
|
||||
'field_name' => 'field_2',
|
||||
'type' => 'test_field',
|
||||
'entity_type' => 'entity_test',
|
||||
];
|
||||
FieldStorageConfig::create($another_field_storage_definition)->save();
|
||||
|
||||
// Create fields for each.
|
||||
$field_definition = [
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
];
|
||||
FieldConfig::create($field_definition)->save();
|
||||
$another_field_definition = $field_definition;
|
||||
$another_field_definition['field_name'] = $another_field_storage_definition['field_name'];
|
||||
FieldConfig::create($another_field_definition)->save();
|
||||
|
||||
// Test that the first field is not deleted, and then delete it.
|
||||
$field_storage_config_storage = \Drupal::entityTypeManager()->getStorage('field_storage_config');
|
||||
$field_storage = current($field_storage_config_storage->loadByProperties([
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'include_deleted' => TRUE,
|
||||
]));
|
||||
$this->assertFalse($field_storage->isDeleted());
|
||||
FieldStorageConfig::loadByName('entity_test', $field_storage_definition['field_name'])->delete();
|
||||
|
||||
// Make sure that the field storage is deleted as it had no data.
|
||||
$field_storages = $field_storage_config_storage->loadByProperties([
|
||||
'field_name' => $field_storage_definition['field_name'],
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $field_storages, 'Field storage was deleted');
|
||||
|
||||
// Make sure that this field is marked as deleted when it is
|
||||
// specifically loaded.
|
||||
$fields = \Drupal::entityTypeManager()
|
||||
->getStorage('field_config')
|
||||
->loadByProperties([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => $field_definition['field_name'],
|
||||
'bundle' => $field_definition['bundle'],
|
||||
'include_deleted' => TRUE,
|
||||
]);
|
||||
$this->assertCount(0, $fields, 'Field storage was deleted');
|
||||
|
||||
// Try to load the storage normally and make sure it does not show up.
|
||||
$field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']);
|
||||
$this->assertEmpty($field_storage, 'Field storage was deleted');
|
||||
|
||||
// Try to load the field normally and make sure it does not show up.
|
||||
$field = FieldConfig::load('entity_test.' . $field_definition['bundle'] . '.' . $field_definition['field_name']);
|
||||
$this->assertEmpty($field, 'Field was deleted');
|
||||
|
||||
// Make sure the other field and its storage are not deleted.
|
||||
$another_field_storage = FieldStorageConfig::load('entity_test.' . $another_field_storage_definition['field_name']);
|
||||
$this->assertFalse($another_field_storage->isDeleted());
|
||||
$another_field = FieldConfig::load('entity_test.' . $another_field_definition['bundle'] . '.' . $another_field_definition['field_name']);
|
||||
$this->assertFalse($another_field->isDeleted());
|
||||
|
||||
// Try to create a new field the same name as a deleted field and
|
||||
// write data into it.
|
||||
FieldStorageConfig::create($field_storage_definition)->save();
|
||||
FieldConfig::create($field_definition)->save();
|
||||
$field_storage = FieldStorageConfig::load('entity_test.' . $field_storage_definition['field_name']);
|
||||
$this->assertFalse($field_storage->isDeleted());
|
||||
$field = FieldConfig::load('entity_test.' . $field_definition['bundle'] . '.' . $field_definition['field_name']);
|
||||
$this->assertFalse($field->isDeleted());
|
||||
|
||||
// Save an entity with data for the field
|
||||
$entity = EntityTest::create();
|
||||
$values[0]['value'] = mt_rand(1, 127);
|
||||
$entity->{$field_storage->getName()}->value = $values[0]['value'];
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
|
||||
// Verify the field is present on load
|
||||
$this->assertCount(1, $entity->{$field_storage->getName()}, "Data in previously deleted field saves and loads correctly");
|
||||
foreach ($values as $delta => $value) {
|
||||
$this->assertEquals($values[$delta]['value'], $entity->{$field_storage->getName()}[$delta]->value, "Data in previously deleted field saves and loads correctly");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that updating a field storage type is not allowed.
|
||||
*/
|
||||
public function testUpdateFieldType(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_type',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
try {
|
||||
$field_storage->set('type', 'integer');
|
||||
$field_storage->save();
|
||||
$this->fail('Cannot update a field to a different type.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldException::class, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing a field storage type.
|
||||
*/
|
||||
public function testUpdateEntityType(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_type',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$this->expectException(FieldException::class);
|
||||
$this->expectExceptionMessage('Cannot change the field type for an existing field storage. The field storage entity_test.field_type has the type decimal.');
|
||||
|
||||
$field_storage->set('type', 'foobar');
|
||||
$field_storage->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests changing a field storage entity type.
|
||||
*/
|
||||
public function testUpdateEntityTargetType(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_type',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'decimal',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$this->expectException(FieldException::class);
|
||||
$this->expectExceptionMessage('Cannot change the entity type for an existing field storage. The field storage foobar.field_type has the type entity_test.');
|
||||
|
||||
$field_storage->set('entity_type', 'foobar');
|
||||
$field_storage->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests updating a field storage.
|
||||
*/
|
||||
public function testUpdate(): void {
|
||||
// Create a field with a defined cardinality, so that we can ensure it's
|
||||
// respected. Since cardinality enforcement is consistent across database
|
||||
// systems, it makes a good test case.
|
||||
$cardinality = 4;
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_update',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => $cardinality,
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
do {
|
||||
$entity = EntityTest::create();
|
||||
// Fill in the entity with more values than $cardinality.
|
||||
for ($i = 0; $i < 20; $i++) {
|
||||
// We can not use $i here because 0 values are filtered out.
|
||||
$entity->field_update[] = $i + 1;
|
||||
}
|
||||
// Load back and assert there are $cardinality number of values.
|
||||
$entity = $this->entitySaveReload($entity);
|
||||
$this->assertCount($field_storage->getCardinality(), $entity->field_update);
|
||||
// Now check the values themselves.
|
||||
for ($delta = 0; $delta < $cardinality; $delta++) {
|
||||
$this->assertEquals($delta + 1, $entity->field_update[$delta]->value);
|
||||
}
|
||||
// Increase $cardinality and set the field cardinality to the new value.
|
||||
$field_storage->setCardinality(++$cardinality);
|
||||
$field_storage->save();
|
||||
} while ($cardinality < 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field type modules forbidding an update.
|
||||
*/
|
||||
public function testUpdateForbid(): void {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'forbidden',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
'settings' => [
|
||||
'changeable' => 0,
|
||||
'unchangeable' => 0,
|
||||
],
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field_storage->setSetting('changeable', $field_storage->getSetting('changeable') + 1);
|
||||
try {
|
||||
$field_storage->save();
|
||||
}
|
||||
catch (FieldStorageDefinitionUpdateForbiddenException $e) {
|
||||
$this->fail('An unchangeable setting cannot be updated.');
|
||||
}
|
||||
$field_storage->setSetting('unchangeable', $field_storage->getSetting('unchangeable') + 1);
|
||||
try {
|
||||
$field_storage->save();
|
||||
$this->fail('An unchangeable setting can be updated.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->assertInstanceOf(FieldStorageDefinitionUpdateForbiddenException::class, $e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests discovery of field type categories provided by modules.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldTypeCategoryDiscoveryTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_plugins_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* Tests custom field type categories created by modules.
|
||||
*/
|
||||
public function testFieldTypeCategories(): void {
|
||||
$category = \Drupal::service('plugin.manager.field.field_type_category')->createInstance('test_category');
|
||||
$expected = [
|
||||
'Test category',
|
||||
'This is a test field type category.',
|
||||
-10,
|
||||
['field_plugins_test/test_library'],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, [
|
||||
(string) $category->getLabel(),
|
||||
(string) $category->getDescription(),
|
||||
$category->getWeight(),
|
||||
$category->getLibraries(),
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Extension\ExtensionDiscovery;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
|
||||
/**
|
||||
* Tests the field type manager.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldTypePluginManagerTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests the default settings convenience methods.
|
||||
*/
|
||||
public function testDefaultSettings(): void {
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
foreach (['test_field', 'shape', 'hidden_test_field'] as $type) {
|
||||
$definition = $field_type_manager->getDefinition($type);
|
||||
$this->assertSame($field_type_manager->getDefaultStorageSettings($type), $definition['class']::defaultStorageSettings(), "$type storage settings were returned");
|
||||
$this->assertSame($field_type_manager->getDefaultFieldSettings($type), $definition['class']::defaultFieldSettings(), "$type field settings were returned");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of field item instances.
|
||||
*/
|
||||
public function testCreateInstance(): void {
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
foreach (['test_field', 'shape', 'hidden_test_field'] as $type) {
|
||||
$definition = $field_type_manager->getDefinition($type);
|
||||
|
||||
$class = $definition['class'];
|
||||
$field_name = 'field_' . $type;
|
||||
|
||||
$field_definition = BaseFieldDefinition::create($type);
|
||||
|
||||
$configuration = [
|
||||
'field_definition' => $field_definition,
|
||||
'name' => $field_name,
|
||||
'parent' => NULL,
|
||||
];
|
||||
|
||||
$instance = $field_type_manager->createInstance($type, $configuration);
|
||||
|
||||
$this->assertInstanceOf($class, $instance);
|
||||
$this->assertEquals($field_name, $instance->getName(), "Instance name is $field_name");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of field item instances.
|
||||
*/
|
||||
public function testCreateInstanceWithConfig(): void {
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$type = 'test_field';
|
||||
$definition = $field_type_manager->getDefinition($type);
|
||||
|
||||
$class = $definition['class'];
|
||||
$field_name = 'field_' . $type;
|
||||
|
||||
$field_definition = BaseFieldDefinition::create($type)
|
||||
->setLabel('Jenny')
|
||||
->setDefaultValue(8675309);
|
||||
|
||||
$configuration = [
|
||||
'field_definition' => $field_definition,
|
||||
'name' => $field_name,
|
||||
'parent' => NULL,
|
||||
];
|
||||
|
||||
$entity = EntityTest::create();
|
||||
|
||||
$instance = $field_type_manager->createInstance($type, $configuration);
|
||||
|
||||
$this->assertInstanceOf($class, $instance);
|
||||
$this->assertEquals($field_name, $instance->getName(), "Instance name is $field_name");
|
||||
$this->assertEquals('Jenny', $instance->getFieldDefinition()->getLabel(), 'Instance label is Jenny');
|
||||
$this->assertEquals([['value' => 8675309]], $instance->getFieldDefinition()->getDefaultValue($entity), 'Instance default_value is 8675309');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests all field items provide an existing main property.
|
||||
*/
|
||||
public function testMainProperty(): void {
|
||||
// Let's enable all Drupal modules in Drupal core, so we test any field
|
||||
// type plugin.
|
||||
$this->enableAllCoreModules();
|
||||
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
foreach ($field_type_manager->getDefinitions() as $plugin_id => $definition) {
|
||||
$class = $definition['class'];
|
||||
$property = $class::mainPropertyName();
|
||||
if ($property === NULL) {
|
||||
continue;
|
||||
}
|
||||
$storage_definition = BaseFieldDefinition::create($plugin_id);
|
||||
$property_definitions = $class::propertyDefinitions($storage_definition);
|
||||
$properties = implode(', ', array_keys($property_definitions));
|
||||
if (!empty($property_definitions)) {
|
||||
$message = sprintf("%s property %s found in %s", $plugin_id, $property, $properties);
|
||||
$this->assertArrayHasKey($property, $class::propertyDefinitions($storage_definition), $message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests UI definitions per entity type.
|
||||
*/
|
||||
public function testUiDefinitionsPerEntityType(): void {
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager */
|
||||
$field_type_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$definitions = $field_type_manager->getEntityTypeUiDefinitions('node');
|
||||
$this->assertEquals('Boolean (overridden by alter)', (string) $definitions['boolean']['label']);
|
||||
$definitions = $field_type_manager->getEntityTypeUiDefinitions('entity_test');
|
||||
$this->assertEquals('Boolean', (string) $definitions['boolean']['label']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all core modules.
|
||||
*/
|
||||
protected function enableAllCoreModules(): void {
|
||||
$listing = new ExtensionDiscovery($this->root);
|
||||
$module_list = $listing->scan('module', FALSE);
|
||||
/** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
|
||||
$module_handler = $this->container->get('module_handler');
|
||||
$module_list = array_filter(array_keys($module_list), function ($module) use ($module_handler, $module_list) {
|
||||
return !$module_handler->moduleExists($module) && str_starts_with($module_list[$module]->getPath(), 'core');
|
||||
});
|
||||
$this->enableModules($module_list);
|
||||
}
|
||||
|
||||
}
|
||||
102
web/core/modules/field/tests/src/Kernel/FieldValidationTest.php
Normal file
102
web/core/modules/field/tests/src/Kernel/FieldValidationTest.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
/**
|
||||
* Tests field validation.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FieldValidationTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $entityType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $bundle;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityInterface
|
||||
*/
|
||||
private $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a field and storage of type 'test_field', on the 'entity_test'
|
||||
// entity type.
|
||||
$this->entityType = 'entity_test';
|
||||
$this->bundle = 'entity_test';
|
||||
$this->createFieldWithStorage('', $this->entityType, $this->bundle);
|
||||
|
||||
// Create an 'entity_test' entity.
|
||||
$this->entity = \Drupal::entityTypeManager()->getStorage($this->entityType)->create([
|
||||
'type' => $this->bundle,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the number of values is validated against the field cardinality.
|
||||
*/
|
||||
public function testCardinalityConstraint(): void {
|
||||
$cardinality = $this->fieldTestData->field_storage->getCardinality();
|
||||
$entity = $this->entity;
|
||||
|
||||
for ($delta = 0; $delta < $cardinality + 1; $delta++) {
|
||||
$entity->{$this->fieldTestData->field_name}[] = ['value' => 1];
|
||||
}
|
||||
|
||||
// Validate the field.
|
||||
$violations = $entity->{$this->fieldTestData->field_name}->validate();
|
||||
|
||||
// Check that the expected constraint violations are reported.
|
||||
$this->assertCount(1, $violations);
|
||||
$this->assertEquals('', $violations[0]->getPropertyPath());
|
||||
$this->assertEquals(sprintf('%s: this field cannot hold more than %s values.', $this->fieldTestData->field->getLabel(), $cardinality), $violations[0]->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that constraints defined by the field type are validated.
|
||||
*/
|
||||
public function testFieldConstraints(): void {
|
||||
$cardinality = $this->fieldTestData->field_storage->getCardinality();
|
||||
$entity = $this->entity;
|
||||
|
||||
// The test is only valid if the field cardinality is greater than 1.
|
||||
$this->assertGreaterThan(1, $cardinality);
|
||||
|
||||
// Set up values for the field.
|
||||
$expected_violations = [];
|
||||
for ($delta = 0; $delta < $cardinality; $delta++) {
|
||||
// All deltas except '1' have incorrect values.
|
||||
if ($delta == 1) {
|
||||
$value = 1;
|
||||
}
|
||||
else {
|
||||
$value = -1;
|
||||
$expected_violations[$delta . '.value'][] = $this->fieldTestData->field->getLabel() . ' does not accept the value -1.';
|
||||
}
|
||||
$entity->{$this->fieldTestData->field_name}[] = $value;
|
||||
}
|
||||
|
||||
// Validate the field.
|
||||
$violations = $entity->{$this->fieldTestData->field_name}->validate();
|
||||
|
||||
// Check that the expected constraint violations are reported.
|
||||
$violations_by_path = [];
|
||||
foreach ($violations as $violation) {
|
||||
$violations_by_path[$violation->getPropertyPath()][] = $violation->getMessage();
|
||||
}
|
||||
$this->assertEquals($expected_violations, $violations_by_path);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel;
|
||||
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
|
||||
/**
|
||||
* Tests the field formatter plugin manager.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class FormatterPluginManagerTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests that getInstance falls back on default if current is not applicable.
|
||||
*
|
||||
* @see \Drupal\field\Tests\WidgetPluginManagerTest::testNotApplicableFallback()
|
||||
*/
|
||||
public function testNotApplicableFallback(): void {
|
||||
/** @var \Drupal\Core\Field\FormatterPluginManager $formatter_plugin_manager */
|
||||
$formatter_plugin_manager = \Drupal::service('plugin.manager.field.formatter');
|
||||
|
||||
$base_field_definition = BaseFieldDefinition::create('test_field')
|
||||
// Set a name that will make isApplicable() return TRUE.
|
||||
->setName('field_test_field');
|
||||
|
||||
$formatter_options = [
|
||||
'field_definition' => $base_field_definition,
|
||||
'view_mode' => 'default',
|
||||
'configuration' => [
|
||||
'type' => 'field_test_applicable',
|
||||
],
|
||||
];
|
||||
|
||||
$instance = $formatter_plugin_manager->getInstance($formatter_options);
|
||||
$this->assertEquals('field_test_applicable', $instance->getPluginId());
|
||||
|
||||
// Now set name to something that makes isApplicable() return FALSE.
|
||||
$base_field_definition->setName('deny_applicable');
|
||||
$instance = $formatter_plugin_manager->getInstance($formatter_options);
|
||||
|
||||
// Instance should be default widget.
|
||||
$this->assertNotSame('field_test_applicable', $instance->getPluginId());
|
||||
$this->assertEquals('field_test_default', $instance->getPluginId());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\KernelString;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the raw string formatter.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class RawStringFormatterTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field', 'text',
|
||||
'entity_test',
|
||||
'system',
|
||||
'filter',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Configure the theme system.
|
||||
$this->installConfig(['system', 'field']);
|
||||
$this->installEntitySchema('entity_test');
|
||||
|
||||
$this->entityType = 'entity_test';
|
||||
$this->bundle = $this->entityType;
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityType,
|
||||
'type' => 'string_long',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$this->display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'string',
|
||||
'settings' => [],
|
||||
]);
|
||||
$this->display->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders fields of a given entity with a given display.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity object with attached fields to render.
|
||||
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
||||
* The display to render the fields in.
|
||||
*
|
||||
* @return string
|
||||
* The rendered entity fields.
|
||||
*/
|
||||
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||
$content = $display->build($entity);
|
||||
$content = $this->render($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string formatter output.
|
||||
*/
|
||||
public function testStringFormatter(): void {
|
||||
$value = $this->randomString();
|
||||
$value .= "\n\n<strong>" . $this->randomString() . '</strong>';
|
||||
$value .= "\n\n" . $this->randomString();
|
||||
|
||||
$entity = EntityTest::create([]);
|
||||
$entity->{$this->fieldName}->value = $value;
|
||||
|
||||
// Verify that all HTML is escaped and newlines are retained.
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertNoRaw($value);
|
||||
$this->assertRaw(nl2br(Html::escape($value)));
|
||||
|
||||
// Verify the cache tags.
|
||||
$build = $entity->{$this->fieldName}->view();
|
||||
$this->assertTrue(!isset($build[0]['#cache']), 'The string formatter has no cache tags.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\KernelString;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\FieldableEntityInterface;
|
||||
use Drupal\entity_test\Entity\EntityTestLabel;
|
||||
use Drupal\entity_test\Entity\EntityTestRev;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the creation of text fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class StringFormatterTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field',
|
||||
'text',
|
||||
'entity_test',
|
||||
'system',
|
||||
'filter',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $entityType;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $bundle;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface
|
||||
*/
|
||||
protected $display;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Configure the theme system.
|
||||
$this->installConfig(['system', 'field']);
|
||||
$this->installEntitySchema('entity_test_rev');
|
||||
$this->setUpCurrentUser(permissions: [
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
]);
|
||||
|
||||
$this->entityType = 'entity_test_rev';
|
||||
$this->bundle = $this->entityType;
|
||||
$this->fieldName = $this->randomMachineName();
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $this->fieldName,
|
||||
'entity_type' => $this->entityType,
|
||||
'type' => 'string',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->bundle,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$this->display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($this->entityType, $this->bundle)
|
||||
->setComponent($this->fieldName, [
|
||||
'type' => 'string',
|
||||
'settings' => [],
|
||||
]);
|
||||
$this->display->save();
|
||||
|
||||
$this->entityTypeManager = \Drupal::entityTypeManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders fields of a given entity with a given display.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\FieldableEntityInterface $entity
|
||||
* The entity object with attached fields to render.
|
||||
* @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
|
||||
* The display to render the fields in.
|
||||
*
|
||||
* @return string
|
||||
* The rendered entity fields.
|
||||
*/
|
||||
protected function renderEntityFields(FieldableEntityInterface $entity, EntityViewDisplayInterface $display) {
|
||||
$content = $display->build($entity);
|
||||
$content = $this->render($content);
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string formatter output.
|
||||
*/
|
||||
public function testStringFormatter(): void {
|
||||
$value = $this->randomString();
|
||||
$value .= "\n\n<strong>" . $this->randomString() . '</strong>';
|
||||
$value .= "\n\n" . $this->randomString();
|
||||
|
||||
$entity = EntityTestRev::create(['name' => 'view revision']);
|
||||
$entity->{$this->fieldName}->value = $value;
|
||||
|
||||
// Verify that all HTML is escaped and newlines are retained.
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertNoRaw($value);
|
||||
$this->assertRaw(nl2br(Html::escape($value)));
|
||||
|
||||
// Verify the cache tags.
|
||||
$build = $entity->{$this->fieldName}->view();
|
||||
$this->assertTrue(!isset($build[0]['#cache']), 'The string formatter has no cache tags.');
|
||||
|
||||
$value = $this->randomMachineName();
|
||||
$entity->{$this->fieldName}->value = $value;
|
||||
$entity->save();
|
||||
|
||||
// Set the formatter to link to the entity.
|
||||
$this->display->setComponent($this->fieldName, [
|
||||
'type' => 'string',
|
||||
'settings' => [
|
||||
'link_to_entity' => TRUE,
|
||||
],
|
||||
]);
|
||||
$this->display->save();
|
||||
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertLink($value, 0);
|
||||
$this->assertLinkByHref($entity->toUrl()->toString());
|
||||
|
||||
// $entity->toUrl('revision') falls back to the canonical URL if this is no
|
||||
// revision.
|
||||
$this->assertLinkByHref($entity->toUrl('revision')->toString());
|
||||
|
||||
// Make the entity a new revision.
|
||||
$old_revision_id = $entity->getRevisionId();
|
||||
$entity->setNewRevision(TRUE);
|
||||
$value2 = $this->randomMachineName();
|
||||
$entity->{$this->fieldName}->value = $value2;
|
||||
$entity->save();
|
||||
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||
$storage = $this->entityTypeManager->getStorage('entity_test_rev');
|
||||
$entity_new_revision = $storage->loadRevision($old_revision_id);
|
||||
|
||||
$this->renderEntityFields($entity, $this->display);
|
||||
$this->assertLink($value2, 0);
|
||||
$this->assertLinkByHref($entity->toUrl('revision')->toString());
|
||||
|
||||
$this->renderEntityFields($entity_new_revision, $this->display);
|
||||
$this->assertLink($value, 0);
|
||||
$this->assertLinkByHref('/entity_test_rev/' . $entity_new_revision->id() . '/revision/' . $entity_new_revision->getRevisionId() . '/view');
|
||||
|
||||
// Check that linking to a revisionable entity works if the entity type does
|
||||
// not specify a 'revision' link template.
|
||||
$entity_type = clone $this->entityTypeManager->getDefinition('entity_test_rev');
|
||||
$link_templates = $entity_type->getLinkTemplates();
|
||||
unset($link_templates['revision']);
|
||||
$entity_type->set('links', $link_templates);
|
||||
\Drupal::state()->set('entity_test_rev.entity_type', $entity_type);
|
||||
$this->entityTypeManager->clearCachedDefinitions();
|
||||
|
||||
$this->renderEntityFields($entity_new_revision, $this->display);
|
||||
$this->assertLink($value, 0);
|
||||
$this->assertLinkByHref($entity->toUrl('canonical')->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test "link_to_entity" feature on fields which are added to config entity.
|
||||
*/
|
||||
public function testLinkToContentForEntitiesWithNoCanonicalPath(): void {
|
||||
$this->enableModules(['entity_test']);
|
||||
$this->installEntitySchema('entity_test_label');
|
||||
$field_name = 'test_field_name';
|
||||
$entity_type = $bundle = 'entity_test_label';
|
||||
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => $entity_type,
|
||||
'type' => 'string',
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$instance = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $entity_type,
|
||||
'label' => $this->randomMachineName(),
|
||||
]);
|
||||
$instance->save();
|
||||
|
||||
$display = \Drupal::service('entity_display.repository')
|
||||
->getViewDisplay($entity_type, $bundle)
|
||||
->setComponent($field_name, [
|
||||
'type' => 'string',
|
||||
'settings' => [
|
||||
'link_to_entity' => TRUE,
|
||||
],
|
||||
'region' => 'content',
|
||||
]);
|
||||
$display->save();
|
||||
|
||||
$value = $this->randomMachineName();
|
||||
$entity = EntityTestLabel::create(['name' => 'test']);
|
||||
$entity->{$field_name}->value = $value;
|
||||
$entity->save();
|
||||
|
||||
$this->renderEntityFields($entity, $display);
|
||||
$this->assertRaw($value);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\KernelString;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\user\Traits\UserCreationTrait;
|
||||
|
||||
/**
|
||||
* Tests the output of a UUID field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class UuidFormatterTest extends KernelTestBase {
|
||||
|
||||
use UserCreationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field', 'entity_test', 'system', 'user'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installConfig(['system', 'field']);
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('user');
|
||||
$this->setUpCurrentUser(permissions: [
|
||||
'view test entity',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests string formatter output.
|
||||
*/
|
||||
public function testUuidStringFormatter(): void {
|
||||
$entity = EntityTest::create([]);
|
||||
$entity->save();
|
||||
|
||||
$uuid_field = $entity->get('uuid');
|
||||
|
||||
// Verify default render.
|
||||
$render_array = $uuid_field->view([]);
|
||||
$this->assertSame($entity->uuid(), $render_array[0]['#context']['value'], 'The rendered UUID matches the entity UUID.');
|
||||
$this->assertStringContainsString($entity->uuid(), $this->render($render_array), 'The rendered UUID found.');
|
||||
|
||||
// Verify customized render.
|
||||
$render_array = $uuid_field->view(['settings' => ['link_to_entity' => TRUE]]);
|
||||
$this->assertSame('link', $render_array[0]['#type']);
|
||||
$this->assertSame($entity->uuid(), $render_array[0]['#title']['#context']['value']);
|
||||
$this->assertSame($entity->toUrl()->toString(), $render_array[0]['#url']->toString());
|
||||
$rendered = $this->render($render_array);
|
||||
$this->assertStringContainsString($entity->uuid(), $rendered, 'The rendered UUID found.');
|
||||
$this->assertStringContainsString($entity->toUrl()->toString(), $rendered, 'The rendered entity URL found.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\KernelString;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\Tests\field\Kernel\FieldKernelTestBase;
|
||||
use Drupal\Component\Uuid\Uuid;
|
||||
|
||||
/**
|
||||
* Tests the UUID field.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class UuidItemTest extends FieldKernelTestBase {
|
||||
|
||||
/**
|
||||
* Tests 'uuid' random values.
|
||||
*/
|
||||
public function testSampleValue(): void {
|
||||
$entity = EntityTest::create([]);
|
||||
$entity->save();
|
||||
|
||||
$uuid_field = $entity->get('uuid');
|
||||
|
||||
// Test the generateSampleValue() method.
|
||||
$uuid_field->generateSampleItems();
|
||||
$this->assertTrue(Uuid::isValid($uuid_field->value));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,251 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Upgrade field formatter settings to entity.display.*.*.yml.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldFormatterSettingsTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['comment', 'menu_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig(['comment']);
|
||||
$this->executeMigration('d6_comment_type');
|
||||
$this->migrateFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a particular component is NOT included in a display.
|
||||
*
|
||||
* @param string $display_id
|
||||
* The display ID.
|
||||
* @param string $component_id
|
||||
* The component ID.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertComponentNotExists(string $display_id, string $component_id): void {
|
||||
$component = EntityViewDisplay::load($display_id)->getComponent($component_id);
|
||||
$this->assertNull($component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that migrated entity display settings can be loaded using D8 API's.
|
||||
*/
|
||||
public function testEntityDisplaySettings(): void {
|
||||
// Run tests.
|
||||
$field_name = "field_test";
|
||||
$expected = [
|
||||
'type' => 'text_trimmed',
|
||||
'label' => 'above',
|
||||
'settings' => ['trim_length' => 600],
|
||||
'third_party_settings' => [],
|
||||
'weight' => 1,
|
||||
'region' => 'content',
|
||||
];
|
||||
|
||||
// Can we load any entity display.
|
||||
$display = EntityViewDisplay::load('node.story.teaser');
|
||||
$this->assertSame($expected, $display->getComponent($field_name));
|
||||
|
||||
// Test migrate worked with multiple bundles.
|
||||
$display = EntityViewDisplay::load('node.test_page.teaser');
|
||||
$expected['weight'] = 35;
|
||||
$this->assertSame($expected, $display->getComponent($field_name));
|
||||
|
||||
// Test RSS because that has been converted from 4 to rss.
|
||||
$display = EntityViewDisplay::load('node.story.rss');
|
||||
$expected['weight'] = 1;
|
||||
$this->assertSame($expected, $display->getComponent($field_name));
|
||||
|
||||
// Test the default format with text_default which comes from a static map.
|
||||
$expected['type'] = 'text_default';
|
||||
$expected['settings'] = [];
|
||||
$display = EntityViewDisplay::load('node.story.default');
|
||||
$this->assertSame($expected, $display->getComponent($field_name));
|
||||
|
||||
// Check that we can migrate multiple fields.
|
||||
$content = $display->get('content');
|
||||
$this->assertTrue(isset($content['field_test']), 'Settings for field_test exist.');
|
||||
$this->assertTrue(isset($content['field_test_two']), "Settings for field_test_two exist.");
|
||||
|
||||
// Check that we can migrate a field where exclude is not set.
|
||||
$this->assertTrue(isset($content['field_test_exclude_unset']), "Settings for field_test_exclude_unset exist.");
|
||||
|
||||
// Test the number field formatter settings are correct.
|
||||
$expected['weight'] = 1;
|
||||
$expected['type'] = 'number_integer';
|
||||
$expected['settings'] = [
|
||||
'thousand_separator' => ',',
|
||||
'prefix_suffix' => TRUE,
|
||||
];
|
||||
$component = $display->getComponent('field_test_two');
|
||||
$this->assertSame($expected, $component);
|
||||
$expected['weight'] = 2;
|
||||
$expected['type'] = 'number_decimal';
|
||||
$expected['settings'] = [
|
||||
'thousand_separator' => ',',
|
||||
'decimal_separator' => '.',
|
||||
'scale' => 2,
|
||||
'prefix_suffix' => TRUE,
|
||||
];
|
||||
$component = $display->getComponent('field_test_three');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test the email field formatter settings are correct.
|
||||
$expected['weight'] = 6;
|
||||
$expected['type'] = 'email_mailto';
|
||||
$expected['settings'] = [];
|
||||
$component = $display->getComponent('field_test_email');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test the link field formatter settings.
|
||||
$expected['weight'] = 7;
|
||||
$expected['type'] = 'link';
|
||||
$expected['settings'] = [
|
||||
'trim_length' => 80,
|
||||
'url_only' => TRUE,
|
||||
'url_plain' => TRUE,
|
||||
'rel' => '0',
|
||||
'target' => '0',
|
||||
];
|
||||
$component = $display->getComponent('field_test_link');
|
||||
$this->assertSame($expected, $component);
|
||||
$expected['settings']['url_only'] = FALSE;
|
||||
$expected['settings']['url_plain'] = FALSE;
|
||||
$display = EntityViewDisplay::load('node.story.teaser');
|
||||
$component = $display->getComponent('field_test_link');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test the file field formatter settings.
|
||||
$expected['weight'] = 8;
|
||||
$expected['type'] = 'file_default';
|
||||
$expected['settings'] = [
|
||||
'use_description_as_link_text' => TRUE,
|
||||
];
|
||||
$component = $display->getComponent('field_test_filefield');
|
||||
$this->assertSame($expected, $component);
|
||||
$display = EntityViewDisplay::load('node.story.default');
|
||||
$expected['type'] = 'file_url_plain';
|
||||
$expected['settings'] = [];
|
||||
$component = $display->getComponent('field_test_filefield');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test the image field formatter settings.
|
||||
$expected['weight'] = 9;
|
||||
$expected['type'] = 'image';
|
||||
$expected['settings'] = [
|
||||
'image_link' => '',
|
||||
'image_style' => '',
|
||||
'image_loading' => [
|
||||
'attribute' => 'lazy',
|
||||
],
|
||||
];
|
||||
$component = $display->getComponent('field_test_imagefield');
|
||||
$this->assertSame($expected, $component);
|
||||
$display = EntityViewDisplay::load('node.story.teaser');
|
||||
$expected['settings']['image_link'] = 'file';
|
||||
$component = $display->getComponent('field_test_imagefield');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test phone field.
|
||||
$expected['weight'] = 13;
|
||||
$expected['type'] = 'basic_string';
|
||||
$expected['settings'] = [];
|
||||
$component = $display->getComponent('field_test_phone');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test date field.
|
||||
$expected['weight'] = 10;
|
||||
$expected['type'] = 'datetime_default';
|
||||
$expected['settings'] = ['timezone_override' => '', 'format_type' => 'fallback'];
|
||||
$component = $display->getComponent('field_test_date');
|
||||
$this->assertSame($expected, $component);
|
||||
$display = EntityViewDisplay::load('node.story.default');
|
||||
$expected['settings']['format_type'] = 'long';
|
||||
$component = $display->getComponent('field_test_date');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test date stamp field.
|
||||
$expected['weight'] = 11;
|
||||
$expected['settings']['format_type'] = 'fallback';
|
||||
$component = $display->getComponent('field_test_datestamp');
|
||||
$this->assertSame($expected, $component);
|
||||
$display = EntityViewDisplay::load('node.story.teaser');
|
||||
$expected['settings'] = ['timezone_override' => '', 'format_type' => 'medium'];
|
||||
$component = $display->getComponent('field_test_datestamp');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test datetime field.
|
||||
$expected['weight'] = 12;
|
||||
$expected['settings'] = ['timezone_override' => '', 'format_type' => 'short'];
|
||||
$component = $display->getComponent('field_test_datetime');
|
||||
$this->assertSame($expected, $component);
|
||||
$display = EntityViewDisplay::load('node.story.default');
|
||||
$expected['settings']['format_type'] = 'fallback';
|
||||
$component = $display->getComponent('field_test_datetime');
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Test a date field with a random format which should be mapped
|
||||
// to datetime_default.
|
||||
$display = EntityViewDisplay::load('node.story.rss');
|
||||
$expected['settings']['format_type'] = 'fallback';
|
||||
$component = $display->getComponent('field_test_datetime');
|
||||
$this->assertSame($expected, $component);
|
||||
// Test that our Id map has the correct data.
|
||||
$this->assertSame(
|
||||
[['node', 'story', 'teaser', 'field_test']],
|
||||
$this->getMigration('d6_field_formatter_settings')
|
||||
->getIdMap()
|
||||
->lookupDestinationIds(['story', 'teaser', 'node', 'field_test'])
|
||||
);
|
||||
|
||||
// Test hidden field.
|
||||
$this->assertComponentNotExists('node.test_planet.teaser', 'field_test_text_single_checkbox');
|
||||
|
||||
// Test a node reference field, which should be migrated to an entity
|
||||
// reference field.
|
||||
$display = EntityViewDisplay::load('node.employee.default');
|
||||
$component = $display->getComponent('field_company');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('entity_reference_label', $component['type']);
|
||||
// The default node reference formatter shows the referenced node's title
|
||||
// as a link.
|
||||
$this->assertTrue($component['settings']['link']);
|
||||
|
||||
$display = EntityViewDisplay::load('node.employee.teaser');
|
||||
$component = $display->getComponent('field_company');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('entity_reference_label', $component['type']);
|
||||
// The plain node reference formatter shows the referenced node's title,
|
||||
// unlinked.
|
||||
$this->assertFalse($component['settings']['link']);
|
||||
|
||||
$component = $display->getComponent('field_commander');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('entity_reference_label', $component['type']);
|
||||
// The default user reference formatter links to the referenced user.
|
||||
$this->assertTrue($component['settings']['link']);
|
||||
|
||||
$display = EntityViewDisplay::load('comment.comment_node_a_thirty_two_char.default');
|
||||
$component = $display->getComponent('comment_body');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('text_default', $component['type']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Tests\migrate\Kernel\MigrateDumpAlterInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of field label and description translations.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
* @group #slow
|
||||
*/
|
||||
class MigrateFieldInstanceLabelDescriptionTest extends MigrateDrupal6TestBase implements MigrateDumpAlterInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'config_translation',
|
||||
'locale',
|
||||
'language',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installConfig(['node']);
|
||||
$this->installSchema('node', ['node_access']);
|
||||
$this->executeMigration('language');
|
||||
$this->executeMigration('d6_field_instance_label_description_translation');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function migrateDumpAlter(KernelTestBase $test): void {
|
||||
$db = Database::getConnection('default', 'migrate');
|
||||
// Alter the database to test the migration is successful when a translated
|
||||
// field is deleted but the translation data for that field remains in both
|
||||
// the i18n_strings and locales_target tables.
|
||||
$db->delete('content_node_field_instance')
|
||||
->condition('field_name', 'field_test')
|
||||
->condition('type_name', 'story')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of field label and description translations.
|
||||
*/
|
||||
public function testFieldInstanceLabelDescriptionTranslationMigration(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
// Tests fields on 'story' node type.
|
||||
// Check that the deleted field with translations was skipped.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test');
|
||||
$this->assertNull($config_translation->get('label'));
|
||||
$this->assertNull($config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_two');
|
||||
$this->assertSame("fr - Integer Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example integer field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_four');
|
||||
$this->assertSame("fr - Float Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example float field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_email');
|
||||
$this->assertSame("fr - Email Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example email field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_imagefield');
|
||||
$this->assertSame("fr - Image Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example image field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('zu', 'field.field.node.story.field_test_imagefield');
|
||||
$this->assertSame("zu - Image Field", $config_translation->get('label'));
|
||||
$this->assertSame("zu - An example image field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_filefield');
|
||||
$this->assertSame("fr - File Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example file field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_link');
|
||||
$this->assertSame("fr - Link Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example link field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_date');
|
||||
$this->assertSame("fr - Date Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example date field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_datetime');
|
||||
$this->assertSame("fr - Datetime Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example datetime field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_datestamp');
|
||||
$this->assertSame("fr - Date Stamp Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example date stamp field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_date');
|
||||
$this->assertSame("fr - Date Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example date field.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_phone');
|
||||
$this->assertSame("fr - Phone Field", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example phone field.", $config_translation->get('description'));
|
||||
|
||||
// Tests fields on 'test_page' node type.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.test_page.field_test');
|
||||
$this->assertSame("Champ de texte", $config_translation->get('label'));
|
||||
$this->assertSame("fr - An example text field.", $config_translation->get('description'));
|
||||
|
||||
// Tests fields on 'test_planet' node type.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.test_planet.field_multivalue');
|
||||
$this->assertSame("fr - Decimal Field", $config_translation->get('label'));
|
||||
$this->assertSame("Un exemple plusieurs valeurs champ décimal.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.test_planet.field_test_text_single_checkbox');
|
||||
$this->assertNull($config_translation->get('label'));
|
||||
$this->assertSame('fr - An example text field using a single on/off checkbox.', $config_translation->get('description'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field instance option translations.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldInstanceOptionTranslationTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'config_translation',
|
||||
'language',
|
||||
'locale',
|
||||
'menu_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig(['node']);
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd6_node_type',
|
||||
'd6_field',
|
||||
'd6_field_instance',
|
||||
'd6_field_option_translation',
|
||||
'd6_field_instance_option_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of file variables to file.settings.yml.
|
||||
*/
|
||||
public function testFieldInstanceOptionTranslation(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
/** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_float_single_checkbox');
|
||||
$option_translation = ['on_label' => 'fr - 1.234'];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('zu', 'field.field.node.story.field_test_float_single_checkbox');
|
||||
$option_translation = ['on_label' => 'zu - 1.234'];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_text_single_checkbox');
|
||||
$option_translation = [
|
||||
'off_label' => 'fr - Hello',
|
||||
'on_label' => 'fr - Goodbye',
|
||||
];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.story.field_test_text_single_checkbox2');
|
||||
$option_translation = [
|
||||
'off_label' => 'fr - Off',
|
||||
'on_label' => 'fr - Hello',
|
||||
];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('zu', 'field.field.node.story.field_test_text_single_checkbox2');
|
||||
$option_translation = ['on_label' => 'zu - Hello'];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\link\LinkItemInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
use Drupal\node\Entity\Node;
|
||||
|
||||
/**
|
||||
* Migrate field instances.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldInstanceTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['comment', 'menu_ui', 'node'];
|
||||
|
||||
/**
|
||||
* Tests migration of file variables to file.settings.yml.
|
||||
*/
|
||||
public function testFieldInstanceMigration(): void {
|
||||
$this->migrateFields();
|
||||
$this->installConfig(['comment']);
|
||||
$this->executeMigration('d6_comment_type');
|
||||
|
||||
$entity = Node::create(['type' => 'story']);
|
||||
// Test a text field.
|
||||
/** @var \Drupal\field\FieldConfigInterface $field */
|
||||
$field = FieldConfig::load('node.story.field_test');
|
||||
$this->assertSame('Text Field', $field->label());
|
||||
// field_test is a text_long field, which have no settings.
|
||||
$this->assertSame(['allowed_formats' => []], $field->getSettings());
|
||||
$this->assertSame('text for default value', $entity->field_test->value);
|
||||
|
||||
// Test a number field.
|
||||
$field = FieldConfig::load('node.story.field_test_two');
|
||||
$this->assertSame('Integer Field', $field->label());
|
||||
$expected = [
|
||||
'min' => 10,
|
||||
'max' => 100,
|
||||
'prefix' => 'pref',
|
||||
'suffix' => 'suf',
|
||||
'unsigned' => FALSE,
|
||||
'size' => 'normal',
|
||||
];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
|
||||
$field = FieldConfig::load('node.story.field_test_four');
|
||||
$this->assertSame('Float Field', $field->label());
|
||||
$expected = [
|
||||
'min' => 100.0,
|
||||
'max' => 200.0,
|
||||
'prefix' => 'id-',
|
||||
'suffix' => '',
|
||||
];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
|
||||
// Test email field.
|
||||
$field = FieldConfig::load('node.story.field_test_email');
|
||||
$this->assertSame('Email Field', $field->label());
|
||||
$this->assertSame('benjy@example.com', $entity->field_test_email->value);
|
||||
|
||||
// Test image field.
|
||||
$field = FieldConfig::load('node.story.field_test_imagefield');
|
||||
$this->assertSame('Image Field', $field->label());
|
||||
$field_settings = $field->getSettings();
|
||||
$this->assertSame('', $field_settings['max_resolution']);
|
||||
$this->assertSame('', $field_settings['min_resolution']);
|
||||
$this->assertSame('', $field_settings['file_directory']);
|
||||
$this->assertSame('png gif jpg jpeg', $field_settings['file_extensions']);
|
||||
$this->assertSame('public', $field_settings['uri_scheme']);
|
||||
|
||||
// Test a filefield.
|
||||
$field = FieldConfig::load('node.story.field_test_filefield');
|
||||
$this->assertSame('File Field', $field->label());
|
||||
$expected = [
|
||||
'file_extensions' => 'txt pdf doc',
|
||||
'file_directory' => 'images',
|
||||
'description_field' => TRUE,
|
||||
'max_filesize' => '200KB',
|
||||
'target_type' => 'file',
|
||||
'display_field' => FALSE,
|
||||
'display_default' => FALSE,
|
||||
'uri_scheme' => 'public',
|
||||
'handler' => 'default:file',
|
||||
'handler_settings' => [],
|
||||
];
|
||||
$field_settings = $field->getSettings();
|
||||
ksort($expected);
|
||||
ksort($field_settings);
|
||||
// This is the only way to compare arrays.
|
||||
$this->assertSame($expected, $field_settings);
|
||||
|
||||
// Test a link field.
|
||||
$field = FieldConfig::load('node.story.field_test_link');
|
||||
$this->assertSame('Link Field', $field->label());
|
||||
$expected = ['title' => 2, 'link_type' => LinkItemInterface::LINK_GENERIC];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
$this->assertSame('default link title', $entity->field_test_link->title, 'Field field_test_link default title is correct.');
|
||||
$this->assertSame('https://www.drupal.org', $entity->field_test_link->uri);
|
||||
$this->assertSame([], $entity->field_test_link->options['attributes']);
|
||||
|
||||
// Test date field.
|
||||
$field = FieldConfig::load('node.story.field_test_date');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('Date Field', $field->label());
|
||||
$this->assertSame('An example date field.', $field->getDescription());
|
||||
$expected = ['datetime_type' => 'datetime'];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
$expected = [
|
||||
[
|
||||
'default_date_type' => 'relative',
|
||||
'default_date' => 'blank',
|
||||
],
|
||||
];
|
||||
$this->assertSame($expected, $field->getDefaultValueLiteral());
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
|
||||
// Test datetime field.
|
||||
$field = FieldConfig::load('node.story.field_test_datetime');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('Datetime Field', $field->label());
|
||||
$this->assertSame('An example datetime field.', $field->getDescription());
|
||||
$expected = ['datetime_type' => 'datetime'];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
$expected = [];
|
||||
$this->assertSame($expected, $field->getDefaultValueLiteral());
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
|
||||
// Test datestamp field.
|
||||
$field = FieldConfig::load('node.story.field_test_datestamp');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('Date Stamp Field', $field->label());
|
||||
$this->assertSame('An example date stamp field.', $field->getDescription());
|
||||
$expected = [];
|
||||
$this->assertSame($expected, $field->getSettings());
|
||||
$expected = [];
|
||||
$this->assertSame($expected, $field->getDefaultValueLiteral());
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
|
||||
// Test a node reference field, migrated to entity reference.
|
||||
$field = FieldConfig::load('node.employee.field_company');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('entity_reference', $field->getType());
|
||||
$this->assertSame('Company', $field->label());
|
||||
$this->assertSame('default:node', $field->getSetting('handler'));
|
||||
$this->assertSame([], $field->getSetting('handler_settings'));
|
||||
$this->assertSame('node', $field->getSetting('target_type'));
|
||||
$this->assertSame([], $field->getDefaultValueLiteral());
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
|
||||
// Test a user reference field, migrated to entity reference.
|
||||
$field = FieldConfig::load('node.employee.field_commander');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertSame('entity_reference', $field->getType());
|
||||
$this->assertSame('Commanding Officer', $field->label());
|
||||
$this->assertSame('default:user', $field->getSetting('handler'));
|
||||
$this->assertSame([], $field->getSetting('handler_settings'));
|
||||
$this->assertSame('user', $field->getSetting('target_type'));
|
||||
$this->assertSame([], $field->getDefaultValueLiteral());
|
||||
$this->assertTrue($field->isTranslatable());
|
||||
|
||||
// Test a synchronized field is not translatable.
|
||||
$field = FieldConfig::load('node.employee.field_sync');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertFalse($field->isTranslatable());
|
||||
|
||||
// Test a comment with a long name.
|
||||
$field = FieldConfig::load('comment.comment_node_a_thirty_two_char.comment_body');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migrating fields into non-existent content types.
|
||||
*/
|
||||
public function testMigrateFieldIntoUnknownNodeType(): void {
|
||||
$this->sourceDatabase->delete('node_type')
|
||||
->condition('type', 'test_planet')
|
||||
->execute();
|
||||
// The field migrations use the migration plugin to ensure that the node
|
||||
// types exist, so this should produce no failures...
|
||||
$this->migrateFields();
|
||||
|
||||
// ...and the field instances should not have been migrated.
|
||||
$this->assertNull(FieldConfig::load('node.test_planet.field_multivalue'));
|
||||
$this->assertNull(FieldConfig::load('node.test_planet.field_test_text_single_checkbox'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field option translations.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldOptionTranslationTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'config_translation',
|
||||
'language',
|
||||
'locale',
|
||||
'menu_ui',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd6_field',
|
||||
'd6_field_option_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 field to Drupal 8 migration.
|
||||
*/
|
||||
public function testFieldOptionTranslation(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
// Test a select list with allowed values of key only.
|
||||
/** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_test_integer_selectlist');
|
||||
$allowed_values = [
|
||||
1 => [
|
||||
'label' => 'fr - 2341',
|
||||
],
|
||||
3 => [
|
||||
'label' => 'fr - 4123',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('zu', 'field.storage.node.field_test_integer_selectlist');
|
||||
$allowed_values = [
|
||||
1 => [
|
||||
'label' => 'zu - 2341',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
// Test a select list with allowed values of key|label.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_test_string_selectlist');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Noir',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('zu', 'field.storage.node.field_test_string_selectlist');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Okumnyama',
|
||||
],
|
||||
1 => [
|
||||
'label' => 'Mhlophe',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Migrate fields.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->executeMigration('d6_field');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 6 field to Drupal 8 migration.
|
||||
*/
|
||||
public function testFields(): void {
|
||||
// Text field.
|
||||
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
|
||||
$field_storage = FieldStorageConfig::load('node.field_test');
|
||||
$this->assertSame('text_long', $field_storage->getType());
|
||||
// text_long fields do not have settings.
|
||||
$this->assertSame([], $field_storage->getSettings());
|
||||
|
||||
// Integer field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_two');
|
||||
$this->assertSame("integer", $field_storage->getType());
|
||||
|
||||
// Float field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_three');
|
||||
$this->assertSame("decimal", $field_storage->getType());
|
||||
|
||||
// Link field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_link');
|
||||
$this->assertSame("link", $field_storage->getType());
|
||||
|
||||
// File field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_filefield');
|
||||
$this->assertSame("file", $field_storage->getType());
|
||||
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_imagefield');
|
||||
$this->assertSame("image", $field_storage->getType());
|
||||
$settings = $field_storage->getSettings();
|
||||
$this->assertSame('file', $settings['target_type']);
|
||||
$this->assertSame('public', $settings['uri_scheme']);
|
||||
$this->assertSame([], array_filter($settings['default_image']));
|
||||
|
||||
// Phone field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_phone');
|
||||
$this->assertSame("telephone", $field_storage->getType());
|
||||
|
||||
// Date field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_datetime');
|
||||
$this->assertSame("datetime", $field_storage->getType());
|
||||
|
||||
// Date fields.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_datetime');
|
||||
$this->assertSame("datetime", $field_storage->getType());
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_datestamp');
|
||||
$this->assertSame("timestamp", $field_storage->getType());
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_date');
|
||||
$this->assertSame("datetime", $field_storage->getType());
|
||||
|
||||
// Decimal field with radio buttons.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_decimal_radio_buttons');
|
||||
$this->assertSame("list_float", $field_storage->getType());
|
||||
$this->assertSame('1.2', $field_storage->getSetting('allowed_values')['1.2']);
|
||||
$this->assertSame('2.1', $field_storage->getSetting('allowed_values')['2.1']);
|
||||
|
||||
// Email field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_email');
|
||||
$this->assertSame("email", $field_storage->getType());
|
||||
|
||||
// Float field with a single checkbox.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_float_single_checkbox');
|
||||
$this->assertSame("boolean", $field_storage->getType());
|
||||
|
||||
// Integer field with a select list.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_integer_selectlist');
|
||||
$this->assertSame("list_integer", $field_storage->getType());
|
||||
$this->assertSame('1234', $field_storage->getSetting('allowed_values')['1234']);
|
||||
$this->assertSame('2341', $field_storage->getSetting('allowed_values')['2341']);
|
||||
$this->assertSame('3412', $field_storage->getSetting('allowed_values')['3412']);
|
||||
$this->assertSame('4123', $field_storage->getSetting('allowed_values')['4123']);
|
||||
|
||||
// Text field with a single checkbox.
|
||||
$field_storage = FieldStorageConfig::load('node.field_test_text_single_checkbox');
|
||||
$this->assertSame("boolean", $field_storage->getType());
|
||||
|
||||
// Test a node reference field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_company');
|
||||
$this->assertInstanceOf(FieldStorageConfig::class, $field_storage);
|
||||
$this->assertSame('entity_reference', $field_storage->getType());
|
||||
$this->assertSame('node', $field_storage->getSetting('target_type'));
|
||||
|
||||
// Test a second node reference field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_company_4');
|
||||
$this->assertInstanceOf(FieldStorageConfig::class, $field_storage);
|
||||
$this->assertSame('entity_reference', $field_storage->getType());
|
||||
$this->assertSame('node', $field_storage->getSetting('target_type'));
|
||||
|
||||
// Test a user reference field.
|
||||
$field_storage = FieldStorageConfig::load('node.field_commander');
|
||||
$this->assertInstanceOf(FieldStorageConfig::class, $field_storage);
|
||||
$this->assertSame('entity_reference', $field_storage->getType());
|
||||
$this->assertSame('user', $field_storage->getSetting('target_type'));
|
||||
|
||||
// Node reference to entity reference migration.
|
||||
$field_storage = FieldStorageConfig::load('node.field_node_reference');
|
||||
$this->assertSame('entity_reference', $field_storage->getType());
|
||||
$this->assertSame('node', $field_storage->getSetting('target_type'));
|
||||
|
||||
// User reference to entity reference migration.
|
||||
$field_storage = FieldStorageConfig::load('node.field_user_reference');
|
||||
$this->assertSame('entity_reference', $field_storage->getType());
|
||||
$this->assertSame('user', $field_storage->getSetting('target_type'));
|
||||
|
||||
// Validate that the source count and processed count match up.
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->getMigration('d6_field');
|
||||
$this->assertSame($migration->getSourcePlugin()->count(), $migration->getIdMap()->processedCount());
|
||||
|
||||
// Check that we've reported on a conflict in widget_types.
|
||||
$messages = iterator_to_array($migration->getIdMap()->getMessages());
|
||||
$this->assertCount(1, $messages);
|
||||
$this->assertSame($messages[0]->message, 'Widget types optionwidgets_onoff, text_textfield are used in Drupal 6 field instances: widget type optionwidgets_onoff applied to the Drupal 8 base field');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d6;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field widget settings.
|
||||
*
|
||||
* @group migrate_drupal_6
|
||||
*/
|
||||
class MigrateFieldWidgetSettingsTest extends MigrateDrupal6TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['comment', 'menu_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig(['comment']);
|
||||
$this->executeMigration('d6_comment_type');
|
||||
$this->migrateFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that migrated view modes can be loaded using D8 API's.
|
||||
*/
|
||||
public function testWidgetSettings(): void {
|
||||
// Test the config can be loaded.
|
||||
$form_display = EntityFormDisplay::load('node.story.default');
|
||||
$this->assertNotNull($form_display);
|
||||
|
||||
// Text field.
|
||||
$component = $form_display->getComponent('field_test');
|
||||
$expected = [
|
||||
'type' => 'text_textfield',
|
||||
'weight' => 1,
|
||||
'region' => 'content',
|
||||
'settings' => [
|
||||
'size' => 60,
|
||||
'placeholder' => '',
|
||||
],
|
||||
'third_party_settings' => [],
|
||||
];
|
||||
$this->assertSame($expected, $component, 'Text field settings are correct.');
|
||||
|
||||
// Integer field.
|
||||
$component = $form_display->getComponent('field_test_two');
|
||||
$expected['type'] = 'number';
|
||||
$expected['weight'] = 1;
|
||||
$expected['settings'] = ['placeholder' => ''];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Float field.
|
||||
$component = $form_display->getComponent('field_test_three');
|
||||
$expected['weight'] = 2;
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Email field.
|
||||
$component = $form_display->getComponent('field_test_email');
|
||||
$expected['type'] = 'email_default';
|
||||
$expected['weight'] = 6;
|
||||
$expected['settings'] = ['placeholder' => '', 'size' => 60];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Link field.
|
||||
$component = $form_display->getComponent('field_test_link');
|
||||
$this->assertSame('link_default', $component['type']);
|
||||
$this->assertSame(7, $component['weight']);
|
||||
$this->assertEmpty(array_filter($component['settings']));
|
||||
|
||||
// File field.
|
||||
$component = $form_display->getComponent('field_test_filefield');
|
||||
$expected['type'] = 'file_generic';
|
||||
$expected['weight'] = 8;
|
||||
$expected['settings'] = ['progress_indicator' => 'bar'];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Image field.
|
||||
$component = $form_display->getComponent('field_test_imagefield');
|
||||
$expected['type'] = 'image_image';
|
||||
$expected['weight'] = 9;
|
||||
$expected['settings'] = ['progress_indicator' => 'bar', 'preview_image_style' => 'thumbnail'];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Phone field.
|
||||
$component = $form_display->getComponent('field_test_phone');
|
||||
$expected['type'] = 'telephone_default';
|
||||
$expected['weight'] = 13;
|
||||
$expected['settings'] = ['placeholder' => ''];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
// Date fields.
|
||||
$component = $form_display->getComponent('field_test_date');
|
||||
$expected['type'] = 'datetime_default';
|
||||
$expected['weight'] = 10;
|
||||
$expected['settings'] = [];
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
$component = $form_display->getComponent('field_test_datestamp');
|
||||
$expected['weight'] = 11;
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
$component = $form_display->getComponent('field_test_datetime');
|
||||
$expected['weight'] = 12;
|
||||
$this->assertSame($expected, $component);
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$component = $display_repository->getFormDisplay('node', 'employee', 'default')
|
||||
->getComponent('field_company');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('options_select', $component['type']);
|
||||
|
||||
$component = $display_repository->getFormDisplay('node', 'employee', 'default')
|
||||
->getComponent('field_company_2');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('options_buttons', $component['type']);
|
||||
|
||||
$component = $display_repository->getFormDisplay('node', 'employee', 'default')
|
||||
->getComponent('field_company_3');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('entity_reference_autocomplete_tags', $component['type']);
|
||||
|
||||
$component = $display_repository->getFormDisplay('node', 'employee', 'default')
|
||||
->getComponent('field_company_4');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('entity_reference_autocomplete', $component['type']);
|
||||
|
||||
$component = $display_repository->getFormDisplay('node', 'employee', 'default')
|
||||
->getComponent('field_commander');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('options_select', $component['type']);
|
||||
|
||||
$component = $display_repository->getFormDisplay('comment', 'comment_node_a_thirty_two_char', 'default')
|
||||
->getComponent('comment_body');
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame('text_textarea', $component['type']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of D7 field formatter settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MigrateFieldFormatterSettingsTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'image',
|
||||
'link',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
$this->executeMigrations([
|
||||
'd7_view_modes',
|
||||
'd7_field_formatter_settings',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a view display.
|
||||
*
|
||||
* @param string $id
|
||||
* The view display ID.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(string $id): void {
|
||||
$display = EntityViewDisplay::load($id);
|
||||
$this->assertInstanceOf(EntityViewDisplayInterface::class, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a particular component of a view display.
|
||||
*
|
||||
* @param string $display_id
|
||||
* The view display ID.
|
||||
* @param string $component_id
|
||||
* The component ID.
|
||||
* @param string $type
|
||||
* The expected component type (formatter plugin ID).
|
||||
* @param string $label
|
||||
* The expected label of the component.
|
||||
* @param int $weight
|
||||
* The expected weight of the component.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertComponent(string $display_id, string $component_id, string $type, string $label, int $weight): void {
|
||||
$component = EntityViewDisplay::load($display_id)->getComponent($component_id);
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame($type, $component['type']);
|
||||
$this->assertSame($label, $component['label']);
|
||||
$this->assertSame($weight, $component['weight']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a particular component is NOT included in a display.
|
||||
*
|
||||
* @param string $display_id
|
||||
* The display ID.
|
||||
* @param string $component_id
|
||||
* The component ID.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertComponentNotExists(string $display_id, string $component_id): void {
|
||||
$component = EntityViewDisplay::load($display_id)->getComponent($component_id);
|
||||
$this->assertNull($component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of D7 field formatter settings.
|
||||
*/
|
||||
public function testMigration(): void {
|
||||
$this->assertEntity('comment.comment_node_article.default');
|
||||
$this->assertComponent('comment.comment_node_article.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('comment.comment_node_blog.default');
|
||||
$this->assertComponent('comment.comment_node_blog.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('comment.comment_node_book.default');
|
||||
$this->assertComponent('comment.comment_node_book.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('comment.comment_forum.default');
|
||||
$this->assertComponent('comment.comment_forum.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('comment.comment_node_page.default');
|
||||
$this->assertComponent('comment.comment_node_page.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('comment.comment_node_test_content_type.default');
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'field_integer', 'number_integer', 'above', 1);
|
||||
|
||||
$this->assertEntity('comment.comment_node_a_thirty_two_char.default');
|
||||
$this->assertComponent('comment.comment_node_a_thirty_two_char.default', 'comment_body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('node.article.default');
|
||||
$this->assertComponent('node.article.default', 'body', 'text_default', 'hidden', 0);
|
||||
$this->assertComponent('node.article.default', 'field_tags', 'entity_reference_label', 'above', 10);
|
||||
$this->assertComponent('node.article.default', 'field_image', 'image', 'hidden', -1);
|
||||
$this->assertComponent('node.article.default', 'field_text_plain', 'string', 'above', 11);
|
||||
$this->assertComponent('node.article.default', 'field_text_filtered', 'text_default', 'above', 12);
|
||||
$this->assertComponent('node.article.default', 'field_text_long_plain', 'basic_string', 'above', 14);
|
||||
$this->assertComponent('node.article.default', 'field_text_long_filtered', 'text_default', 'above', 15);
|
||||
$this->assertComponent('node.article.default', 'field_text_sum_filtered', 'text_default', 'above', 18);
|
||||
$this->assertComponent('node.article.default', 'field_reference', 'entity_reference_label', 'above', 20);
|
||||
$this->assertComponent('node.article.default', 'field_reference_2', 'entity_reference_entity_view', 'above', 21);
|
||||
|
||||
$this->assertEntity('node.article.teaser');
|
||||
$this->assertComponent('node.article.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 0);
|
||||
$this->assertComponent('node.article.teaser', 'field_tags', 'entity_reference_label', 'above', 10);
|
||||
$this->assertComponent('node.article.teaser', 'field_image', 'image', 'hidden', -1);
|
||||
|
||||
$this->assertEntity('node.blog.default');
|
||||
$this->assertComponent('node.blog.default', 'body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('node.blog.teaser');
|
||||
$this->assertComponent('node.blog.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 0);
|
||||
$this->assertComponent('node.blog.default', 'field_termplain', 'entity_reference_label', 'above', 13);
|
||||
$this->assertComponent('node.blog.default', 'field_termrss', 'entity_reference_label', 'above', 14);
|
||||
|
||||
$this->assertEntity('node.book.default');
|
||||
$this->assertComponent('node.book.default', 'body', 'text_default', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('node.book.teaser');
|
||||
$this->assertComponent('node.book.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('node.forum.default');
|
||||
$this->assertComponent('node.forum.default', 'body', 'text_default', 'hidden', 11);
|
||||
$this->assertComponent('node.forum.default', 'taxonomy_forums', 'entity_reference_label', 'above', 10);
|
||||
|
||||
$this->assertEntity('node.forum.teaser');
|
||||
$this->assertComponent('node.forum.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 11);
|
||||
$this->assertComponent('node.forum.teaser', 'taxonomy_forums', 'entity_reference_label', 'above', 10);
|
||||
|
||||
$this->assertEntity('node.page.default');
|
||||
$this->assertComponent('node.page.default', 'body', 'text_default', 'hidden', 0);
|
||||
$this->assertComponent('node.page.default', 'field_text_plain', 'string', 'above', 1);
|
||||
$this->assertComponent('node.page.default', 'field_text_filtered', 'text_default', 'above', 2);
|
||||
$this->assertComponent('node.page.default', 'field_text_long_plain', 'basic_string', 'above', 4);
|
||||
$this->assertComponent('node.page.default', 'field_text_long_filtered', 'text_default', 'above', 5);
|
||||
$this->assertComponent('node.page.default', 'field_text_sum_filtered', 'text_default', 'above', 8);
|
||||
|
||||
$this->assertEntity('node.page.teaser');
|
||||
$this->assertComponent('node.page.teaser', 'body', 'text_summary_or_trimmed', 'hidden', 0);
|
||||
|
||||
$this->assertEntity('node.test_content_type.default');
|
||||
$this->assertComponent('node.test_content_type.default', 'field_boolean', 'list_default', 'above', 0);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_email', 'email_mailto', 'above', 1);
|
||||
// Phone formatters are not mapped and should default to basic_string.
|
||||
$this->assertComponent('node.test_content_type.default', 'field_phone', 'basic_string', 'above', 2);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_date', 'datetime_default', 'above', 3);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_date_with_end_time', 'datetime_default', 'above', 4);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_file', 'file_default', 'above', 5);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_float', 'number_decimal', 'above', 6);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_images', 'image', 'above', 7);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_integer', 'number_integer', 'above', 8);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_link', 'link', 'above', 9);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_text_list', 'list_default', 'above', 10);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_integer_list', 'list_default', 'above', 11);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_float_list', 'list_default', 'above', 19);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_long_text', 'text_default', 'above', 12);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_node_entityreference', 'entity_reference_label', 'above', 15);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_user_entityreference', 'entity_reference_label', 'above', 16);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_term_entityreference', 'entity_reference_label', 'above', 17);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_telephone', 'telephone_link', 'above', 21);
|
||||
$this->assertComponentNotExists('node.test_content_type.default', 'field_term_reference');
|
||||
$this->assertComponentNotExists('node.test_content_type.default', 'field_text');
|
||||
|
||||
$this->assertEntity('user.user.default');
|
||||
$this->assertComponent('user.user.default', 'field_file', 'file_default', 'above', 0);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\Tests\migrate\Kernel\MigrateDumpAlterInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration field label and description i18n translations.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateFieldInstanceLabelDescriptionTest extends MigrateDrupal7TestBase implements MigrateDumpAlterInterface {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'config_translation',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'field',
|
||||
'file',
|
||||
'image',
|
||||
'language',
|
||||
'link',
|
||||
'locale',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'system',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installConfig(static::$modules);
|
||||
|
||||
$this->executeMigrations([
|
||||
'd7_node_type',
|
||||
'd7_comment_type',
|
||||
'd7_taxonomy_vocabulary',
|
||||
'd7_field',
|
||||
'd7_field_instance',
|
||||
'd7_field_instance_widget_settings',
|
||||
'language',
|
||||
'd7_field_instance_label_description_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function migrateDumpAlter(KernelTestBase $test): void {
|
||||
$db = Database::getConnection('default', 'migrate');
|
||||
// Alter the database to test the migration is successful when a translated
|
||||
// field is deleted but the translation data for that field remains in both
|
||||
// the i18n_strings and locales_target tables.
|
||||
$db->delete('field_config_instance')
|
||||
->condition('field_name', 'field_image')
|
||||
->condition('bundle', 'article')
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of file variables to file.settings.yml.
|
||||
*/
|
||||
public function testFieldInstanceLabelDescriptionTranslationMigration(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
// Check that the deleted field with translations was skipped.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.article.field_image');
|
||||
$this->assertNull($config_translation->get('label'));
|
||||
$this->assertNull($config_translation->get('description'));
|
||||
|
||||
// Tests fields on 'test_content_type' node type.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.test_content_type.field_email');
|
||||
$this->assertNull($config_translation->get('label'));
|
||||
$this->assertSame("fr - The email help text.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.node.test_content_type.field_email');
|
||||
$this->assertSame("is - Email", $config_translation->get('label'));
|
||||
$this->assertSame("is - The email help text.", $config_translation->get('description'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.node.test_content_type.field_boolean');
|
||||
$this->assertSame("is - Some helpful text.", $config_translation->get('description'));
|
||||
|
||||
// Tests fields on 'article' node type.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.article.body');
|
||||
$this->assertSame("fr - Body", $config_translation->get('label'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.article.field_link');
|
||||
$this->assertSame("fr - Link", $config_translation->get('label'));
|
||||
|
||||
// Tests fields on 'test_vocabulary' vocabulary type.
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.taxonomy_term.test_vocabulary.field_term_reference');
|
||||
$this->assertSame("is - Term Reference", $config_translation->get('label'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field instance option translations.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateFieldInstanceOptionTranslationTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'config_translation',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'file',
|
||||
'image',
|
||||
'language',
|
||||
'link',
|
||||
'locale',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'system',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installConfig(['node']);
|
||||
$this->executeMigration('language');
|
||||
$this->migrateFields();
|
||||
$this->executeMigrations([
|
||||
'd7_field_option_translation',
|
||||
'd7_field_instance_option_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate field instance option translations.
|
||||
*/
|
||||
public function testFieldInstanceOptionTranslation(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
/** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.blog.field_boolean');
|
||||
$this->assertNull($config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.node.blog.field_boolean');
|
||||
$option_translation = [
|
||||
'off_label' => 'is - Off',
|
||||
'on_label' => 'is - 1',
|
||||
];
|
||||
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.test_content_type.field_boolean');
|
||||
$this->assertNull($config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.node.test_content_type.field_boolean');
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.field.node.article.field_checkbox');
|
||||
$option_translation = [
|
||||
'off_label' => 'fr - Stop',
|
||||
'on_label' => 'Go',
|
||||
];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.field.node.article.field_checkbox');
|
||||
$option_translation = [
|
||||
'off_label' => 'is - Stop',
|
||||
'on_label' => 'is - Go',
|
||||
];
|
||||
$this->assertSame($option_translation, $config_translation->get('settings'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,261 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\FieldConfigInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Migrates Drupal 7 field instances.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MigrateFieldInstanceTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'image',
|
||||
'link',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a field config entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
|
||||
* @param string $expected_label
|
||||
* The expected field label.
|
||||
* @param string $expected_field_type
|
||||
* The expected field type.
|
||||
* @param bool $is_required
|
||||
* Whether or not the field is required.
|
||||
* @param bool $expected_translatable
|
||||
* Whether or not the field is expected to be translatable.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(string $id, string $expected_label, string $expected_field_type, bool $is_required, bool $expected_translatable): void {
|
||||
[$expected_entity_type, $expected_bundle, $expected_name] = explode('.', $id);
|
||||
|
||||
/** @var \Drupal\field\FieldConfigInterface $field */
|
||||
$field = FieldConfig::load($id);
|
||||
$this->assertInstanceOf(FieldConfigInterface::class, $field);
|
||||
$this->assertEquals($expected_label, $field->label());
|
||||
$this->assertEquals($expected_field_type, $field->getType());
|
||||
$this->assertEquals($expected_entity_type, $field->getTargetEntityTypeId());
|
||||
$this->assertEquals($expected_bundle, $field->getTargetBundle());
|
||||
$this->assertEquals($expected_name, $field->getName());
|
||||
$this->assertEquals($is_required, $field->isRequired());
|
||||
$this->assertEquals($expected_entity_type . '.' . $expected_name, $field->getFieldStorageDefinition()->id());
|
||||
$this->assertEquals($expected_translatable, $field->isTranslatable());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the settings of a link field config entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
|
||||
* @param int $title_setting
|
||||
* The expected title setting.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertLinkFields(string $id, int $title_setting): void {
|
||||
$field = FieldConfig::load($id);
|
||||
$this->assertSame($title_setting, $field->getSetting('title'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the settings of an entity reference field config entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID in the form ENTITY_TYPE.BUNDLE.FIELD_NAME.
|
||||
* @param string[] $target_bundles
|
||||
* An array of expected target bundles.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntityReferenceFields(string $id, array $target_bundles): void {
|
||||
$field = FieldConfig::load($id);
|
||||
$handler_settings = $field->getSetting('handler_settings');
|
||||
$this->assertArrayHasKey('target_bundles', $handler_settings);
|
||||
foreach ($handler_settings['target_bundles'] as $target_bundle) {
|
||||
$this->assertContains($target_bundle, $target_bundles);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migrating D7 field instances to field_config entities.
|
||||
*/
|
||||
public function testFieldInstances(): void {
|
||||
$this->assertEntity('comment.comment_node_page.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.page.body', 'Body', 'text_with_summary', FALSE, FALSE);
|
||||
$this->assertEntity('comment.comment_node_article.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.article.body', 'Body', 'text_with_summary', FALSE, TRUE);
|
||||
$this->assertEntity('node.article.field_tags', 'Tags', 'entity_reference', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_image', 'Image', 'image', FALSE, TRUE);
|
||||
$this->assertEntity('comment.comment_node_blog.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.blog.body', 'Body', 'text_with_summary', FALSE, TRUE);
|
||||
$this->assertEntity('node.blog.field_file_mfw', 'file_mfw', 'file', FALSE, TRUE);
|
||||
$this->assertEntity('node.blog.field_image_miw', 'image_miw', 'image', FALSE, TRUE);
|
||||
$this->assertEntity('comment.comment_node_book.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.book.body', 'Body', 'text_with_summary', FALSE, FALSE);
|
||||
$this->assertEntity('node.forum.taxonomy_forums', 'Forums', 'entity_reference', TRUE, FALSE);
|
||||
$this->assertEntity('comment.comment_forum.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.forum.body', 'Body', 'text_with_summary', FALSE, FALSE);
|
||||
$this->assertEntity('node.forum.field_event', 'event', 'daterange', FALSE, FALSE);
|
||||
$this->assertEntity('comment.comment_node_test_content_type.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_boolean', 'Boolean', 'boolean', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_email', 'Email', 'email', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_phone', 'Phone', 'telephone', TRUE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_date', 'Date', 'datetime', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_date_with_end_time', 'Date With End Time', 'timestamp', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_file', 'File', 'file', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_float', 'Float', 'float', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_images', 'Images', 'image', TRUE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_integer', 'Integer', 'integer', TRUE, TRUE);
|
||||
$this->assertEntity('node.test_content_type.field_link', 'Link', 'link', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_text_list', 'Text List', 'list_string', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_integer_list', 'Integer List', 'list_integer', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_float_list', 'Float List', 'list_float', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_long_text', 'Long text', 'text_with_summary', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_term_reference', 'Term Reference', 'entity_reference', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_text', 'Text', 'string', FALSE, FALSE);
|
||||
$this->assertEntity('node.test_content_type.field_telephone', 'Telephone', 'telephone', FALSE, FALSE);
|
||||
$this->assertEntity('comment.comment_node_test_content_type.field_integer', 'Integer', 'integer', FALSE, TRUE);
|
||||
$this->assertEntity('comment.comment_node_a_thirty_two_char.comment_body', 'Comment', 'text_long', TRUE, FALSE);
|
||||
$this->assertEntity('user.user.field_file', 'File', 'file', FALSE, FALSE);
|
||||
|
||||
$this->assertLinkFields('node.test_content_type.field_link', DRUPAL_OPTIONAL);
|
||||
$this->assertLinkFields('node.article.field_link', DRUPAL_DISABLED);
|
||||
$this->assertLinkFields('node.blog.field_link', DRUPAL_REQUIRED);
|
||||
|
||||
$this->assertEntityReferenceFields('node.article.field_tags', ['tags']);
|
||||
$this->assertEntityReferenceFields('node.forum.taxonomy_forums', ['sujet_de_discussion']);
|
||||
$this->assertEntityReferenceFields('node.test_content_type.field_term_reference', ['tags', 'test_vocabulary']);
|
||||
|
||||
// Tests that fields created by the Title module are not migrated.
|
||||
$title_field = FieldConfig::load('node.test_content_type.title_field');
|
||||
$this->assertNull($title_field);
|
||||
$subject_field = FieldConfig::load('comment.comment_node_article.subject_field');
|
||||
$this->assertNull($subject_field);
|
||||
$name_field = FieldConfig::load('taxonomy_term.test_vocabulary.name_field');
|
||||
$this->assertNull($name_field);
|
||||
$description_field = FieldConfig::load('taxonomy_term.test_vocabulary.description_field');
|
||||
$this->assertNull($description_field);
|
||||
|
||||
$boolean_field = FieldConfig::load('node.test_content_type.field_boolean');
|
||||
$expected_settings = [
|
||||
'on_label' => '1',
|
||||
'off_label' => 'Off',
|
||||
];
|
||||
$this->assertSame($expected_settings, $boolean_field->get('settings'));
|
||||
|
||||
// Test a synchronized field is not translatable.
|
||||
$field = FieldConfig::load('node.article.field_text_plain');
|
||||
$this->assertInstanceOf(FieldConfig::class, $field);
|
||||
$this->assertFalse($field->isTranslatable());
|
||||
|
||||
// Test the translation settings for taxonomy fields.
|
||||
$this->assertEntity('node.article.field_vocab_fixed', 'vocab_fixed', 'entity_reference', FALSE, TRUE);
|
||||
$this->assertEntity('node.article.field_vocab_localize', 'vocab_localize', 'entity_reference', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_vocab_translate', 'vocab_translate', 'entity_reference', FALSE, TRUE);
|
||||
|
||||
// Test the node and user reference fields.
|
||||
$this->assertEntity('node.article.field_node_reference', 'Node Reference', 'entity_reference', FALSE, TRUE);
|
||||
$this->assertEntity('node.article.field_user_reference', 'User Reference', 'entity_reference', FALSE, TRUE);
|
||||
$expected_handler_settings = [
|
||||
'sort' => [
|
||||
'field' => '_none',
|
||||
'direction' => 'ASC',
|
||||
],
|
||||
'auto_create' => FALSE,
|
||||
'filter' => [
|
||||
'type' => 'role',
|
||||
'role' => [
|
||||
'authenticated user' => 'authenticated user',
|
||||
],
|
||||
],
|
||||
'include_anonymous' => TRUE,
|
||||
];
|
||||
$field = FieldConfig::load('node.article.field_user_reference');
|
||||
$actual = $field->getSetting('handler_settings');
|
||||
$this->assertSame($expected_handler_settings, $actual);
|
||||
|
||||
// Test migration of text field instances with different text processing.
|
||||
// All text and text_long field instances using a field base that has only
|
||||
// plain text instances should be migrated to string and string_long fields.
|
||||
// All text_with_summary field instances using a field base that has only
|
||||
// plain text instances should not have been migrated since there's no such
|
||||
// thing as a string_with_summary field.
|
||||
$this->assertEntity('node.page.field_text_plain', 'Text plain', 'string', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_text_plain', 'Text plain', 'string', FALSE, FALSE);
|
||||
$this->assertEntity('node.page.field_text_long_plain', 'Text long plain', 'string_long', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_text_long_plain', 'Text long plain', 'string_long', FALSE, TRUE);
|
||||
$this->assertNull(FieldConfig::load('node.page.field_text_sum_plain'));
|
||||
$this->assertNull(FieldConfig::load('node.article.field_text_sum_plain'));
|
||||
|
||||
// All text, text_long and text_with_summary field instances using a field
|
||||
// base that has only filtered text instances should be migrated to text,
|
||||
// text_long and text_with_summary fields.
|
||||
$this->assertEntity('node.page.field_text_filtered', 'Text filtered', 'text', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_text_filtered', 'Text filtered', 'text', FALSE, TRUE);
|
||||
$this->assertEntity('node.page.field_text_long_filtered', 'Text long filtered', 'text_long', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_text_long_filtered', 'Text long filtered', 'text_long', FALSE, TRUE);
|
||||
$this->assertEntity('node.page.field_text_sum_filtered', 'Text summary filtered', 'text_with_summary', FALSE, FALSE);
|
||||
$this->assertEntity('node.article.field_text_sum_filtered', 'Text summary filtered', 'text_with_summary', FALSE, TRUE);
|
||||
|
||||
// All text, text_long and text_with_summary field instances using a field
|
||||
// base that has both plain text and filtered text instances should not have
|
||||
// been migrated.
|
||||
$this->assertNull(FieldConfig::load('node.page.field_text_plain_filtered'));
|
||||
$this->assertNull(FieldConfig::load('node.article.field_text_plain_filtered'));
|
||||
$this->assertNull(FieldConfig::load('node.page.field_text_long_plain_filtered'));
|
||||
$this->assertNull(FieldConfig::load('node.article.field_text_long_plain_filtered'));
|
||||
$this->assertNull(FieldConfig::load('node.page.field_text_sum_plain_filtered'));
|
||||
$this->assertNull(FieldConfig::load('node.article.field_text_sum_plain_filtered'));
|
||||
|
||||
// For each text field instances that were skipped, there should be a log
|
||||
// message with the required steps to fix this.
|
||||
$migration = $this->getMigration('d7_field_instance');
|
||||
$errors = array_map(function ($message) {
|
||||
return $message->message;
|
||||
}, iterator_to_array($migration->getIdMap()->getMessages()));
|
||||
$this->assertCount(8, $errors);
|
||||
sort($errors);
|
||||
$message = 'd7_field_instance:type: Can\'t migrate source field field_text_long_plain_filtered configured with both plain text and filtered text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text';
|
||||
$this->assertEquals($errors[0], $message);
|
||||
$this->assertEquals($errors[1], $message);
|
||||
$message = 'd7_field_instance:type: Can\'t migrate source field field_text_plain_filtered configured with both plain text and filtered text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text';
|
||||
$this->assertEquals($errors[2], $message);
|
||||
$this->assertEquals($errors[3], $message);
|
||||
$message = 'd7_field_instance:type: Can\'t migrate source field field_text_sum_plain of type text_with_summary configured with plain text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text';
|
||||
$this->assertEquals($errors[4], $message);
|
||||
$this->assertEquals($errors[5], $message);
|
||||
$message = 'd7_field_instance:type: Can\'t migrate source field field_text_sum_plain_filtered of type text_with_summary configured with plain text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text';
|
||||
$this->assertEquals($errors[6], $message);
|
||||
$this->assertEquals($errors[7], $message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field widget settings.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MigrateFieldInstanceWidgetSettingsTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'image',
|
||||
'link',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->migrateFields();
|
||||
$this->executeMigration('d7_field_instance_widget_settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a form display entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID.
|
||||
* @param string $expected_entity_type
|
||||
* The expected entity type to which the display settings are attached.
|
||||
* @param string $expected_bundle
|
||||
* The expected bundle to which the display settings are attached.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(string $id, string $expected_entity_type, string $expected_bundle): void {
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $entity */
|
||||
$entity = EntityFormDisplay::load($id);
|
||||
$this->assertInstanceOf(EntityFormDisplayInterface::class, $entity);
|
||||
$this->assertSame($expected_entity_type, $entity->getTargetEntityTypeId());
|
||||
$this->assertSame($expected_bundle, $entity->getTargetBundle());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a particular component of a form display.
|
||||
*
|
||||
* @param string $display_id
|
||||
* The form display ID.
|
||||
* @param string $component_id
|
||||
* The component ID.
|
||||
* @param string $widget_type
|
||||
* The expected widget type.
|
||||
* @param int $weight
|
||||
* The expected weight of the component.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertComponent(string $display_id, string $component_id, string $widget_type, int $weight): void {
|
||||
$component = EntityFormDisplay::load($display_id)->getComponent($component_id);
|
||||
$this->assertIsArray($component);
|
||||
$this->assertSame($widget_type, $component['type']);
|
||||
$this->assertSame($weight, $component['weight']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that migrated view modes can be loaded using D8 APIs.
|
||||
*/
|
||||
public function testWidgetSettings(): void {
|
||||
$this->assertEntity('node.page.default', 'node', 'page');
|
||||
$this->assertComponent('node.page.default', 'body', 'text_textarea_with_summary', -4);
|
||||
$this->assertComponent('node.page.default', 'field_text_plain', 'string_textfield', -2);
|
||||
$this->assertComponent('node.page.default', 'field_text_filtered', 'text_textfield', 0);
|
||||
$this->assertComponent('node.page.default', 'field_text_long_plain', 'string_textarea', 4);
|
||||
$this->assertComponent('node.page.default', 'field_text_long_filtered', 'text_textarea', 6);
|
||||
$this->assertComponent('node.page.default', 'field_text_sum_filtered', 'text_textarea_with_summary', 12);
|
||||
|
||||
$this->assertEntity('node.article.default', 'node', 'article');
|
||||
$this->assertComponent('node.article.default', 'body', 'text_textarea_with_summary', -4);
|
||||
$this->assertComponent('node.article.default', 'field_tags', 'entity_reference_autocomplete', -4);
|
||||
$this->assertComponent('node.article.default', 'field_image', 'image_image', -1);
|
||||
$this->assertComponent('node.article.default', 'field_text_plain', 'string_textfield', 11);
|
||||
$this->assertComponent('node.article.default', 'field_text_filtered', 'text_textfield', 12);
|
||||
$this->assertComponent('node.article.default', 'field_text_long_plain', 'string_textarea', 14);
|
||||
$this->assertComponent('node.article.default', 'field_text_long_filtered', 'text_textarea', 15);
|
||||
$this->assertComponent('node.article.default', 'field_text_sum_filtered', 'text_textarea_with_summary', 18);
|
||||
|
||||
$this->assertEntity('node.blog.default', 'node', 'blog');
|
||||
$this->assertComponent('node.blog.default', 'body', 'text_textarea_with_summary', -4);
|
||||
|
||||
$this->assertEntity('node.book.default', 'node', 'book');
|
||||
$this->assertComponent('node.book.default', 'body', 'text_textarea_with_summary', -4);
|
||||
|
||||
$this->assertEntity('node.forum.default', 'node', 'forum');
|
||||
$this->assertComponent('node.forum.default', 'body', 'text_textarea_with_summary', 1);
|
||||
$this->assertComponent('node.forum.default', 'taxonomy_forums', 'options_select', 0);
|
||||
|
||||
$this->assertEntity('node.test_content_type.default', 'node', 'test_content_type');
|
||||
$this->assertComponent('node.test_content_type.default', 'field_boolean', 'boolean_checkbox', 1);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_date', 'datetime_default', 2);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_date_with_end_time', 'datetime_default', 3);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_email', 'email_default', 4);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_file', 'file_generic', 5);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_float', 'number', 7);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_images', 'image_image', 8);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_integer', 'number', 9);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_link', 'link_default', 10);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_integer_list', 'options_buttons', 12);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_long_text', 'text_textarea_with_summary', 13);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_phone', 'telephone_default', 6);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_term_reference', 'entity_reference_autocomplete', 14);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_node_entityreference', 'entity_reference_autocomplete', 16);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_user_entityreference', 'options_buttons', 17);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_term_entityreference', 'entity_reference_autocomplete_tags', 18);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_text', 'string_textfield', 15);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_text_list', 'options_select', 11);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_float_list', 'options_select', 20);
|
||||
$this->assertComponent('node.test_content_type.default', 'field_telephone', 'telephone_default', 21);
|
||||
|
||||
$this->assertEntity('user.user.default', 'user', 'user');
|
||||
$this->assertComponent('user.user.default', 'field_file', 'file_generic', 8);
|
||||
|
||||
$this->assertEntity('comment.comment_node_test_content_type.default', 'comment', 'comment_node_test_content_type');
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'comment_body', 'text_textarea', 0);
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'field_integer', 'number', 2);
|
||||
|
||||
$this->assertEntity('comment.comment_node_a_thirty_two_char.default', 'comment', 'comment_node_a_thirty_two_char');
|
||||
$this->assertComponent('comment.comment_node_a_thirty_two_char.default', 'comment_body', 'text_textarea', 0);
|
||||
|
||||
$this->assertEntity('taxonomy_term.test_vocabulary.default', 'taxonomy_term', 'test_vocabulary');
|
||||
$this->assertComponent('comment.comment_node_test_content_type.default', 'field_integer', 'number', 2);
|
||||
|
||||
$this->assertEntity('node.blog.default', 'node', 'blog');
|
||||
$this->assertComponent('node.blog.default', 'field_file_mfw', 'file_generic', 17);
|
||||
$this->assertComponent('node.blog.default', 'field_image_miw', 'image_image', 18);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Migrate field option translations.
|
||||
*
|
||||
* @group migrate_drupal_7
|
||||
*/
|
||||
class MigrateFieldOptionTranslationTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'config_translation',
|
||||
'datetime',
|
||||
'datetime_range',
|
||||
'file',
|
||||
'image',
|
||||
'language',
|
||||
'link',
|
||||
'locale',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'system',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->executeMigrations([
|
||||
'language',
|
||||
'd7_field',
|
||||
'd7_field_option_translation',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the Drupal 7 field option translation to Drupal 8 migration.
|
||||
*/
|
||||
public function testFieldOptionTranslation(): void {
|
||||
$language_manager = $this->container->get('language_manager');
|
||||
|
||||
/** @var \Drupal\language\Config\LanguageConfigOverride $config_translation */
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_color');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Verte',
|
||||
],
|
||||
1 => [
|
||||
'label' => 'Noire',
|
||||
],
|
||||
2 => [
|
||||
'label' => 'Blanche',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.storage.node.field_color');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Grænn',
|
||||
],
|
||||
1 => [
|
||||
'label' => 'Svartur',
|
||||
],
|
||||
2 => [
|
||||
'label' => 'Hvítur',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_rating');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Haute',
|
||||
],
|
||||
1 => [
|
||||
'label' => 'Moyenne',
|
||||
],
|
||||
2 => [
|
||||
'label' => 'Faible',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.storage.node.field_rating');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
'label' => 'Hár',
|
||||
],
|
||||
1 => [
|
||||
'label' => 'Miðlungs',
|
||||
],
|
||||
2 => [
|
||||
'label' => 'Lágt',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_boolean');
|
||||
$this->assertNull($config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.storage.node.field_boolean');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
0 => 'Off',
|
||||
1 => '1',
|
||||
],
|
||||
1 => [
|
||||
0 => 'Off',
|
||||
1 => '1',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('fr', 'field.storage.node.field_checkbox');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
0 => 'Stop',
|
||||
1 => 'Go',
|
||||
],
|
||||
1 => [
|
||||
0 => 'Stop',
|
||||
1 => 'Go',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
$config_translation = $language_manager->getLanguageConfigOverride('is', 'field.storage.node.field_checkbox');
|
||||
$allowed_values = [
|
||||
0 => [
|
||||
0 => 'Stop',
|
||||
1 => 'Go',
|
||||
],
|
||||
1 => [
|
||||
0 => 'Stop',
|
||||
1 => 'Go',
|
||||
],
|
||||
];
|
||||
$this->assertSame($allowed_values, $config_translation->get('settings.allowed_values'));
|
||||
// Ensure that the count query works as expected.
|
||||
$this->assertCount(20, $this->getMigration('d7_field_option_translation')->getSourcePlugin());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,216 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Migrates Drupal 7 fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MigrateFieldTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* The modules to be enabled during the test.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = [
|
||||
'comment',
|
||||
'datetime',
|
||||
'file',
|
||||
'image',
|
||||
'link',
|
||||
'node',
|
||||
'system',
|
||||
'taxonomy',
|
||||
'telephone',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a field_storage_config entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID in the form ENTITY_TYPE.FIELD_NAME.
|
||||
* @param string $expected_type
|
||||
* The expected field type.
|
||||
* @param bool $expected_translatable
|
||||
* Whether or not the field is expected to be translatable.
|
||||
* @param int $expected_cardinality
|
||||
* The expected cardinality of the field.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(string $id, string $expected_type, bool $expected_translatable, int $expected_cardinality): void {
|
||||
[$expected_entity_type, $expected_name] = explode('.', $id);
|
||||
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $field */
|
||||
$field = FieldStorageConfig::load($id);
|
||||
$this->assertInstanceOf(FieldStorageConfigInterface::class, $field);
|
||||
$this->assertEquals($expected_name, $field->getName());
|
||||
$this->assertEquals($expected_type, $field->getType());
|
||||
$this->assertEquals($expected_translatable, $field->isTranslatable());
|
||||
$this->assertEquals($expected_entity_type, $field->getTargetEntityTypeId());
|
||||
|
||||
if ($expected_cardinality === 1) {
|
||||
$this->assertFalse($field->isMultiple());
|
||||
}
|
||||
else {
|
||||
$this->assertTrue($field->isMultiple());
|
||||
}
|
||||
$this->assertEquals($expected_cardinality, $field->getCardinality());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migrating D7 fields to field_storage_config entities.
|
||||
*/
|
||||
public function testFields(): void {
|
||||
\Drupal::service('module_installer')->install(['datetime_range']);
|
||||
$this->installConfig(static::$modules);
|
||||
$this->executeMigration('d7_field');
|
||||
|
||||
$this->assertEntity('node.body', 'text_with_summary', TRUE, 1);
|
||||
$this->assertEntity('node.field_long_text', 'text_with_summary', TRUE, 1);
|
||||
$this->assertEntity('comment.comment_body', 'text_long', TRUE, 1);
|
||||
$this->assertEntity('node.field_file', 'file', TRUE, 1);
|
||||
$this->assertEntity('user.field_file', 'file', TRUE, 1);
|
||||
$this->assertEntity('node.field_float', 'float', TRUE, 1);
|
||||
$this->assertEntity('node.field_image', 'image', TRUE, 1);
|
||||
$this->assertEntity('node.field_images', 'image', TRUE, 1);
|
||||
$this->assertEntity('node.field_integer', 'integer', TRUE, 1);
|
||||
$this->assertEntity('comment.field_integer', 'integer', TRUE, 1);
|
||||
$this->assertEntity('node.field_integer_list', 'list_integer', TRUE, 1);
|
||||
$this->assertEntity('node.field_link', 'link', TRUE, 1);
|
||||
$this->assertEntity('node.field_tags', 'entity_reference', TRUE, -1);
|
||||
$this->assertEntity('node.field_term_reference', 'entity_reference', TRUE, 1);
|
||||
$this->assertEntity('node.taxonomy_forums', 'entity_reference', TRUE, 1);
|
||||
$this->assertEntity('node.field_text', 'string', TRUE, 1);
|
||||
$this->assertEntity('node.field_text_list', 'list_string', TRUE, 3);
|
||||
$this->assertEntity('node.field_float_list', 'list_float', TRUE, 1);
|
||||
$this->assertEntity('node.field_boolean', 'boolean', TRUE, 1);
|
||||
$this->assertEntity('node.field_email', 'email', TRUE, -1);
|
||||
$this->assertEntity('node.field_phone', 'telephone', TRUE, 1);
|
||||
$this->assertEntity('node.field_date', 'datetime', TRUE, 1);
|
||||
$this->assertEntity('node.field_date_with_end_time', 'timestamp', TRUE, 1);
|
||||
$this->assertEntity('node.field_node_entityreference', 'entity_reference', TRUE, -1);
|
||||
$this->assertEntity('node.field_user_entityreference', 'entity_reference', TRUE, 1);
|
||||
$this->assertEntity('node.field_term_entityreference', 'entity_reference', TRUE, -1);
|
||||
$this->assertEntity('node.field_date_without_time', 'datetime', TRUE, 1);
|
||||
$this->assertEntity('node.field_datetime_without_time', 'datetime', TRUE, 1);
|
||||
$this->assertEntity('node.field_file_mfw', 'file', TRUE, 1);
|
||||
$this->assertEntity('node.field_image_miw', 'image', TRUE, 1);
|
||||
|
||||
// Tests that fields created by the Title module are not migrated.
|
||||
$title_field = FieldStorageConfig::load('node.title_field');
|
||||
$this->assertNull($title_field);
|
||||
$subject_field = FieldStorageConfig::load('comment.subject_field');
|
||||
$this->assertNull($subject_field);
|
||||
$name_field = FieldStorageConfig::load('taxonomy_term.name_field');
|
||||
$this->assertNull($name_field);
|
||||
$description_field = FieldStorageConfig::load('taxonomy_term.description_field');
|
||||
$this->assertNull($description_field);
|
||||
|
||||
// Assert that the taxonomy term reference fields are referencing the
|
||||
// correct entity type.
|
||||
$field = FieldStorageConfig::load('node.field_term_reference');
|
||||
$this->assertEquals('taxonomy_term', $field->getSetting('target_type'));
|
||||
$field = FieldStorageConfig::load('node.taxonomy_forums');
|
||||
$this->assertEquals('taxonomy_term', $field->getSetting('target_type'));
|
||||
|
||||
// Assert that the entityreference fields are referencing the correct
|
||||
// entity type.
|
||||
$field = FieldStorageConfig::load('node.field_node_entityreference');
|
||||
$this->assertEquals('node', $field->getSetting('target_type'));
|
||||
$field = FieldStorageConfig::load('node.field_user_entityreference');
|
||||
$this->assertEquals('user', $field->getSetting('target_type'));
|
||||
$field = FieldStorageConfig::load('node.field_term_entityreference');
|
||||
$this->assertEquals('taxonomy_term', $field->getSetting('target_type'));
|
||||
|
||||
// Make sure that datetime fields get the right datetime_type setting
|
||||
$field = FieldStorageConfig::load('node.field_date');
|
||||
$this->assertEquals('datetime', $field->getSetting('datetime_type'));
|
||||
$field = FieldStorageConfig::load('node.field_date_without_time');
|
||||
$this->assertEquals('date', $field->getSetting('datetime_type'));
|
||||
$field = FieldStorageConfig::load('node.field_datetime_without_time');
|
||||
$this->assertEquals('date', $field->getSetting('datetime_type'));
|
||||
// Except for field_date_with_end_time which is a timestamp and so does not
|
||||
// have a datetime_type setting.
|
||||
$field = FieldStorageConfig::load('node.field_date_with_end_time');
|
||||
$this->assertNull($field->getSetting('datetime_type'));
|
||||
|
||||
// Assert node and user reference fields.
|
||||
$field = FieldStorageConfig::load('node.field_node_reference');
|
||||
$this->assertEquals('node', $field->getSetting('target_type'));
|
||||
$field = FieldStorageConfig::load('node.field_user_reference');
|
||||
$this->assertEquals('user', $field->getSetting('target_type'));
|
||||
|
||||
// Make sure a datetime field with a todate is now a daterange type.
|
||||
$field = FieldStorageConfig::load('node.field_event');
|
||||
$this->assertSame('daterange', $field->getType());
|
||||
$this->assertSame('datetime_range', $field->getTypeProvider());
|
||||
$this->assertEquals('datetime', $field->getSetting('datetime_type'));
|
||||
|
||||
// Test the migration of text fields with different text processing.
|
||||
// All text and text_long field bases that have only plain text instances
|
||||
// should be migrated to string and string_long fields.
|
||||
// All text_with_summary field bases that have only plain text instances
|
||||
// should not have been migrated since there's no such thing as a
|
||||
// string_with_summary field.
|
||||
$this->assertEntity('node.field_text_plain', 'string', TRUE, 1);
|
||||
$this->assertEntity('node.field_text_long_plain', 'string_long', TRUE, 1);
|
||||
$this->assertNull(FieldStorageConfig::load('node.field_text_sum_plain'));
|
||||
|
||||
// All text, text_long and text_with_summary field bases that have only
|
||||
// filtered text instances should be migrated to text, text_long and
|
||||
// text_with_summary fields.
|
||||
$this->assertEntity('node.field_text_filtered', 'text', TRUE, 1);
|
||||
$this->assertEntity('node.field_text_long_filtered', 'text_long', TRUE, 1);
|
||||
$this->assertEntity('node.field_text_sum_filtered', 'text_with_summary', TRUE, 1);
|
||||
|
||||
// All text, text_long and text_with_summary field bases that have both
|
||||
// plain text and filtered text instances should not have been migrated.
|
||||
$this->assertNull(FieldStorageConfig::load('node.field_text_plain_filtered'));
|
||||
$this->assertNull(FieldStorageConfig::load('node.field_text_long_plain_filtered'));
|
||||
$this->assertNull(FieldStorageConfig::load('node.field_text_sum_plain_filtered'));
|
||||
|
||||
// For each text field bases that were skipped, there should be a log
|
||||
// message with the required steps to fix this.
|
||||
$migration = $this->getMigration('d7_field');
|
||||
$errors = array_map(function ($message) {
|
||||
return $message->message;
|
||||
}, iterator_to_array($migration->getIdMap()->getMessages()));
|
||||
sort($errors);
|
||||
$this->assertCount(4, $errors);
|
||||
$this->assertEquals('d7_field:type: Can\'t migrate source field field_text_long_plain_filtered configured with both plain text and filtered text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text', $errors[0]);
|
||||
$this->assertEquals('d7_field:type: Can\'t migrate source field field_text_plain_filtered configured with both plain text and filtered text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text', $errors[1]);
|
||||
$this->assertEquals('d7_field:type: Can\'t migrate source field field_text_sum_plain of type text_with_summary configured with plain text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text', $errors[2]);
|
||||
$this->assertEquals('d7_field:type: Can\'t migrate source field field_text_sum_plain_filtered of type text_with_summary configured with plain text processing. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#plain-text', $errors[3]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migrating D7 datetime fields.
|
||||
*/
|
||||
public function testDatetimeFields(): void {
|
||||
$this->installConfig(static::$modules);
|
||||
$this->executeMigration('d7_field');
|
||||
|
||||
// Datetime field with 'todate' settings is not migrated.
|
||||
$this->assertNull(FieldStorageConfig::load('node.field_event'));
|
||||
|
||||
// Check that we've reported on a conflict in widget_types.
|
||||
// Validate that the source count and processed count match up.
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->getMigration('d7_field');
|
||||
$messages = iterator_to_array($migration->getIdMap()->getMessages());
|
||||
$this->assertCount(5, $messages);
|
||||
$msg = "d7_field:type:process_field: Can't migrate field 'field_event' with 'todate' settings. Enable the datetime_range module. See https://www.drupal.org/docs/8/upgrade/known-issues-when-upgrading-from-drupal-6-or-7-to-drupal-8#datetime";
|
||||
$this->assertSame($messages[4]->message, $msg);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Core\Entity\EntityViewModeInterface;
|
||||
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
|
||||
|
||||
/**
|
||||
* Tests migration of D7 view modes.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class MigrateViewModesTest extends MigrateDrupal7TestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['comment', 'node', 'taxonomy', 'text'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('node');
|
||||
$this->executeMigration('d7_view_modes');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts various aspects of a view mode entity.
|
||||
*
|
||||
* @param string $id
|
||||
* The entity ID.
|
||||
* @param string $label
|
||||
* The expected label of the view mode.
|
||||
* @param string $entity_type
|
||||
* The expected entity type ID which owns the view mode.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertEntity(string $id, string $label, string $entity_type): void {
|
||||
/** @var \Drupal\Core\Entity\EntityViewModeInterface $view_mode */
|
||||
$view_mode = EntityViewMode::load($id);
|
||||
$this->assertInstanceOf(EntityViewModeInterface::class, $view_mode);
|
||||
$this->assertSame($label, $view_mode->label());
|
||||
$this->assertSame($entity_type, $view_mode->getTargetType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests migration of D7 view mode variables to D8 config entities.
|
||||
*/
|
||||
public function testMigration(): void {
|
||||
$this->assertEntity('comment.full', 'Full', 'comment');
|
||||
$this->assertEntity('node.teaser', 'Teaser', 'node');
|
||||
$this->assertEntity('node.full', 'Full', 'node');
|
||||
$this->assertEntity('node.custom', 'custom', 'node');
|
||||
$this->assertEntity('user.full', 'Full', 'user');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
|
||||
/**
|
||||
* Migrates and rolls back Drupal 7 fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class RollbackFieldInstanceTest extends MigrateFieldInstanceTest {
|
||||
|
||||
/**
|
||||
* Tests migrating D7 fields to field_storage_config entities, then rolling back.
|
||||
*/
|
||||
public function testFieldInstances(): void {
|
||||
// Test that the field instances have migrated (prior to rollback).
|
||||
parent::testFieldInstances();
|
||||
|
||||
$this->executeRollback('d7_field_instance');
|
||||
$this->executeRollback('d7_field');
|
||||
|
||||
// Check that field instances have been rolled back.
|
||||
$field_instance_ids = [
|
||||
'comment.comment_node_page.comment_body',
|
||||
'node.page.body',
|
||||
'comment.comment_node_article.comment_body',
|
||||
'node.article.body',
|
||||
'node.article.field_tags',
|
||||
'node.article.field_image',
|
||||
'comment.comment_node_blog.comment_body',
|
||||
'node.blog.body',
|
||||
'comment.comment_node_book.comment_body',
|
||||
'node.book.body',
|
||||
'node.forum.taxonomy_forums',
|
||||
'comment.comment_forum.comment_body',
|
||||
'node.forum.body',
|
||||
'comment.comment_node_test_content_type.comment_body',
|
||||
'node.test_content_type.field_boolean',
|
||||
'node.test_content_type.field_email',
|
||||
'node.test_content_type.field_phone',
|
||||
'node.test_content_type.field_date',
|
||||
'node.test_content_type.field_date_with_end_time',
|
||||
'node.test_content_type.field_file',
|
||||
'node.test_content_type.field_float',
|
||||
'node.test_content_type.field_images',
|
||||
'node.test_content_type.field_integer',
|
||||
'node.test_content_type.field_link',
|
||||
'node.test_content_type.field_text_list',
|
||||
'node.test_content_type.field_integer_list',
|
||||
'node.test_content_type.field_long_text',
|
||||
'node.test_content_type.field_term_reference',
|
||||
'node.test_content_type.field_text',
|
||||
'comment.comment_node_test_content_type.field_integer',
|
||||
'user.user.field_file',
|
||||
];
|
||||
foreach ($field_instance_ids as $field_instance_id) {
|
||||
$this->assertNull(FieldConfig::load($field_instance_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single rollback.
|
||||
*
|
||||
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to rollback, or its ID.
|
||||
*/
|
||||
protected function executeRollback($migration): void {
|
||||
if (is_string($migration)) {
|
||||
$this->migration = $this->getMigration($migration);
|
||||
}
|
||||
else {
|
||||
$this->migration = $migration;
|
||||
}
|
||||
(new MigrateExecutable($this->migration, $this))->rollback();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field\Kernel\Migrate\d7;
|
||||
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
|
||||
/**
|
||||
* Migrates and rolls back Drupal 7 fields.
|
||||
*
|
||||
* @group field
|
||||
*/
|
||||
class RollbackFieldTest extends MigrateFieldTest {
|
||||
|
||||
/**
|
||||
* Tests migrating D7 fields to field_storage_config entities, then rolling back.
|
||||
*/
|
||||
public function testFields(): void {
|
||||
// Test that the fields have migrated (prior to rollback).
|
||||
parent::testFields();
|
||||
|
||||
$this->executeRollback('d7_field');
|
||||
|
||||
// Check that fields have been rolled back.
|
||||
$rolled_back_field_ids = [
|
||||
'comment.field_integer',
|
||||
'node.taxonomy_forums',
|
||||
'node.field_integer',
|
||||
'node.field_tags',
|
||||
'node.field_term_reference',
|
||||
'node.field_text_list',
|
||||
'node.field_text',
|
||||
'node.field_phone',
|
||||
'node.field_file',
|
||||
'node.field_images',
|
||||
'node.field_image',
|
||||
'node.field_long_text',
|
||||
'node.field_date_with_end_time',
|
||||
'node.field_integer_list',
|
||||
'node.field_date',
|
||||
'node.field_link',
|
||||
'node.field_float',
|
||||
'node.field_boolean',
|
||||
'node.field_email',
|
||||
'user.field_file',
|
||||
];
|
||||
foreach ($rolled_back_field_ids as $field_id) {
|
||||
$this->assertNull(FieldStorageConfig::load($field_id));
|
||||
}
|
||||
|
||||
// Check that fields that should persist have not been rolled back.
|
||||
$non_rolled_back_field_ids = [
|
||||
'node.body',
|
||||
'comment.comment_body',
|
||||
];
|
||||
foreach ($non_rolled_back_field_ids as $field_id) {
|
||||
$this->assertNotNull(FieldStorageConfig::load($field_id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single rollback.
|
||||
*
|
||||
* @param string|\Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to rollback, or its ID.
|
||||
*/
|
||||
protected function executeRollback($migration): void {
|
||||
if (is_string($migration)) {
|
||||
$this->migration = $this->getMigration($migration);
|
||||
}
|
||||
else {
|
||||
$this->migration = $migration;
|
||||
}
|
||||
(new MigrateExecutable($this->migration, $this))->rollback();
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user