Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,5 @@
|
||||
name: 'Field UI test'
|
||||
type: module
|
||||
description: 'Support module for Field UI tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_ui_test\Hook;
|
||||
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Field\FieldConfigInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_ui_test.
|
||||
*/
|
||||
class FieldUiTestHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_ENTITY_TYPE_access().
|
||||
*/
|
||||
#[Hook('field_config_access')]
|
||||
public function fieldConfigAccess(FieldConfigInterface $field): AccessResultInterface {
|
||||
return AccessResult::forbiddenIf($field->getName() == 'highlander');
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_BASE_ID_alter().
|
||||
*/
|
||||
#[Hook('form_entity_view_display_edit_form_alter')]
|
||||
public function formEntityViewDisplayEditFormAlter(&$form, FormStateInterface $form_state) : void {
|
||||
$table =& $form['fields'];
|
||||
foreach (Element::children($table) as $name) {
|
||||
$table[$name]['parent_wrapper']['parent']['#options'] = ['indent' => 'Indent'];
|
||||
$table[$name]['parent_wrapper']['parent']['#default_value'] = 'indent';
|
||||
}
|
||||
$table['indent'] = [
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'draggable',
|
||||
'field-group',
|
||||
],
|
||||
'id' => 'indent-id',
|
||||
],
|
||||
'#row_type' => 'group',
|
||||
'#region_callback' => [$this, 'regionCallback'],
|
||||
'#js_settings' => [
|
||||
'rowHandler' => 'group',
|
||||
],
|
||||
'human_name' => [
|
||||
'#markup' => 'Indent',
|
||||
'#prefix' => '<span class="group-label">',
|
||||
'#suffix' => '</span>',
|
||||
],
|
||||
'weight' => [
|
||||
'#type' => 'textfield',
|
||||
'#default_value' => 0,
|
||||
'#size' => 3,
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'field-weight',
|
||||
],
|
||||
],
|
||||
],
|
||||
'parent_wrapper' => [
|
||||
'parent' => [
|
||||
'#type' => 'select',
|
||||
'#options' => [
|
||||
'indent' => 'Indent',
|
||||
],
|
||||
'#empty_value' => '',
|
||||
'#default_value' => '',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'field-parent',
|
||||
],
|
||||
],
|
||||
'#parents' => [
|
||||
'fields',
|
||||
'indent',
|
||||
'parent',
|
||||
],
|
||||
],
|
||||
'hidden_name' => [
|
||||
'#type' => 'hidden',
|
||||
'#default_value' => 'indent',
|
||||
'#attributes' => [
|
||||
'class' => [
|
||||
'field-name',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function regionCallback($row): string {
|
||||
return 'content';
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
name: 'Field UI test deprecated'
|
||||
type: module
|
||||
description: 'Support module for testing deprecated Field UI functionality.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\field_ui_test_deprecated\Hook;
|
||||
|
||||
use Drupal\field\FieldStorageConfigInterface;
|
||||
use Drupal\field_ui\Form\FieldStorageConfigEditForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for field_ui_test_deprecated.
|
||||
*/
|
||||
class FieldUiTestDeprecatedHooks {
|
||||
|
||||
/**
|
||||
* Implements hook_form_FORM_ID_alter() for field_storage_config_edit_form.
|
||||
*/
|
||||
#[Hook('form_field_storage_config_edit_form_alter')]
|
||||
public function formFieldStorageConfigEditFormAlter(&$form, FormStateInterface $form_state) : void {
|
||||
if (!$form_state->getFormObject() instanceof FieldStorageConfigEditForm) {
|
||||
throw new \LogicException('field_storage_config_edit_form() expects to get access to the field storage config entity edit form.');
|
||||
}
|
||||
if (!$form_state->getFormObject()->getEntity() instanceof FieldStorageConfigInterface) {
|
||||
throw new \LogicException('field_storage_config_edit_form() expects to get access to the field storage config entity.');
|
||||
}
|
||||
if (!isset($form['cardinality_container']['cardinality'])) {
|
||||
throw new \LogicException('field_storage_config_edit_form() expects to that the cardinality container with the cardinality form element exists.');
|
||||
}
|
||||
$form['cardinality_container']['hello'] = ['#markup' => 'Greetings from the field_storage_config_edit_form() alter.'];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the UI for configuring entity displays.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityDisplayFormBaseTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'entity_test', 'field_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
foreach (EntityTestHelper::getEntityTypes() as $entity_type) {
|
||||
// Auto-create fields for testing.
|
||||
FieldStorageConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => 'field_test_no_plugin',
|
||||
'type' => 'field_test',
|
||||
'cardinality' => 1,
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => $entity_type,
|
||||
'field_name' => 'field_test_no_plugin',
|
||||
'bundle' => $entity_type,
|
||||
'label' => 'Test field with no plugin',
|
||||
'translatable' => FALSE,
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay($entity_type, $entity_type)
|
||||
->setComponent('field_test_no_plugin', [
|
||||
'type' => 'test_field_widget',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer entity_test form display',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the entity is not affected when there are no applicable formatters.
|
||||
*/
|
||||
public function testNoApplicableFormatters(): void {
|
||||
$storage = $this->container->get('entity_type.manager')->getStorage('entity_form_display');
|
||||
$id = 'entity_test.entity_test.default';
|
||||
|
||||
$entity_before = $storage->load($id);
|
||||
$this->drupalGet('entity_test/structure/entity_test/form-display');
|
||||
$entity_after = $storage->load($id);
|
||||
|
||||
$this->assertSame($entity_before->toArray(), $entity_after->toArray());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,220 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the entity display modes UI.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityDisplayModeTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'entity_test', 'field_ui', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a node type.
|
||||
$this->drupalCreateContentType([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the EntityViewMode user interface.
|
||||
*/
|
||||
public function testEntityViewModeUI(): void {
|
||||
// Test the listing page.
|
||||
$this->drupalGet('admin/structure/display-modes/view');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer display modes']));
|
||||
$this->drupalGet('admin/structure/display-modes/view');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Add view mode');
|
||||
$this->assertSession()->linkByHrefExists('admin/structure/display-modes/view/add');
|
||||
$this->assertSession()->linkByHrefExists('admin/structure/display-modes/view/add/entity_test');
|
||||
|
||||
$this->drupalGet('admin/structure/display-modes/view/add/entity_test_mulrev');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
|
||||
$this->drupalGet('admin/structure/display-modes/view/add');
|
||||
$this->assertSession()->linkNotExists('Test entity - revisions and data table', 'An entity type with no view builder cannot have view modes.');
|
||||
|
||||
// Test adding a view mode including dots in machine_name.
|
||||
$this->clickLink('Test entity');
|
||||
// Check if 'Name' field is required.
|
||||
$this->assertTrue($this->getSession()->getPage()->findField('label')->hasClass('required'));
|
||||
$edit = [
|
||||
'id' => $this->randomMachineName() . '.' . $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
|
||||
|
||||
// Test adding a view mode.
|
||||
$edit = [
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Saved the {$edit['label']} view mode.");
|
||||
|
||||
// Test editing the view mode.
|
||||
$this->drupalGet('admin/structure/display-modes/view/manage/entity_test.' . $edit['id']);
|
||||
|
||||
// Test that the link templates added by field_ui_entity_type_build() are
|
||||
// generating valid routes.
|
||||
$view_mode = EntityViewMode::load('entity_test.' . $edit['id']);
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_view_mode.collection')->toString(), $view_mode->toUrl('collection')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_view_mode.add_form', ['entity_type_id' => $view_mode->getTargetType()])->toString(), $view_mode->toUrl('add-form')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_view_mode.edit_form', ['entity_view_mode' => $view_mode->id()])->toString(), $view_mode->toUrl('edit-form')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_view_mode.delete_form', ['entity_view_mode' => $view_mode->id()])->toString(), $view_mode->toUrl('delete-form')->toString());
|
||||
|
||||
// Test deleting the view mode.
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to delete the view mode {$edit['label']}?");
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains("The view mode {$edit['label']} has been deleted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the EntityFormMode user interface.
|
||||
*/
|
||||
public function testEntityFormModeUI(): void {
|
||||
// Test the listing page.
|
||||
$this->drupalGet('admin/structure/display-modes/form');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer display modes']));
|
||||
$this->drupalGet('admin/structure/display-modes/form');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('Add form mode');
|
||||
$this->assertSession()->linkByHrefExists('admin/structure/display-modes/form/add');
|
||||
|
||||
$this->drupalGet('admin/structure/display-modes/form/add/entity_test_no_label');
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
|
||||
$this->drupalGet('admin/structure/display-modes/form/add');
|
||||
$this->assertSession()->linkNotExists('Entity Test without label', 'An entity type with no form cannot have form modes.');
|
||||
|
||||
// Test adding a view mode including dots in machine_name.
|
||||
$this->clickLink('Test entity');
|
||||
// Check if 'Name' field is required.
|
||||
$this->assertTrue($this->getSession()->getPage()->findField('label')->hasClass('required'));
|
||||
$edit = [
|
||||
'id' => $this->randomMachineName() . '.' . $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name must contain only lowercase letters, numbers, and underscores.');
|
||||
|
||||
// Test adding a form mode.
|
||||
$edit = [
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
'description' => $this->randomString(),
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Saved the {$edit['label']} form mode.");
|
||||
|
||||
// Test editing the form mode.
|
||||
$this->drupalGet('admin/structure/display-modes/form/manage/entity_test.' . $edit['id']);
|
||||
|
||||
// Test that the link templates added by field_ui_entity_type_build() are
|
||||
// generating valid routes.
|
||||
$form_mode = EntityFormMode::load('entity_test.' . $edit['id']);
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_form_mode.collection')->toString(), $form_mode->toUrl('collection')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_form_mode.add_form', ['entity_type_id' => $form_mode->getTargetType()])->toString(), $form_mode->toUrl('add-form')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_form_mode.edit_form', ['entity_form_mode' => $form_mode->id()])->toString(), $form_mode->toUrl('edit-form')->toString());
|
||||
$this->assertEquals(Url::fromRoute('entity.entity_form_mode.delete_form', ['entity_form_mode' => $form_mode->id()])->toString(), $form_mode->toUrl('delete-form')->toString());
|
||||
|
||||
// Test deleting the form mode.
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to delete the form mode {$edit['label']}?");
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains("The form mode {$edit['label']} has been deleted.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if view modes appear in alphabetical order by visible name.
|
||||
*
|
||||
* The machine name should not be used for sorting.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2858569
|
||||
*/
|
||||
public function testAlphabeticalDisplaySettings(): void {
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'administer content types',
|
||||
'administer display modes',
|
||||
'administer nodes',
|
||||
'administer node fields',
|
||||
'administer node display',
|
||||
'administer node form display',
|
||||
'view the administration theme',
|
||||
]));
|
||||
$this->drupalGet('admin/structure/types/manage/article/display');
|
||||
// Verify that the order of view modes is alphabetical by visible label.
|
||||
// Since the default view modes all have machine names which coincide with
|
||||
// the English labels, they should appear in alphabetical order, by default
|
||||
// if viewing the site in English and if no changes have been made. We will
|
||||
// verify this first.
|
||||
$page_text = $this->getTextContent();
|
||||
$start = strpos($page_text, 'view modes');
|
||||
$pos = $start;
|
||||
$list = ['Full content', 'RSS', 'Search index', 'Search result', 'Teaser'];
|
||||
// Verify that the order of the view modes is correct on the page.
|
||||
foreach ($list as $name) {
|
||||
$new_pos = strpos($page_text, $name, $start);
|
||||
$this->assertGreaterThan($pos, $new_pos);
|
||||
$pos = $new_pos;
|
||||
}
|
||||
// Now that we have verified the original display order, we can change the
|
||||
// label for one of the view modes. If we rename "Teaser" to "Breezier", it
|
||||
// should appear as the first of the listed view modes:
|
||||
// Set new values and enable test plugins.
|
||||
$edit = [
|
||||
'label' => 'Breezier',
|
||||
];
|
||||
$this->drupalGet('admin/structure/display-modes/view/manage/node.teaser');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('Saved the Breezier view mode.');
|
||||
|
||||
// Re-open the display settings for the article content type and verify
|
||||
// that changing "Teaser" to "Breezier" makes it appear before "Full
|
||||
// content".
|
||||
$this->drupalGet('admin/structure/types/manage/article/display');
|
||||
$page_text = $this->getTextContent();
|
||||
$start = strpos($page_text, 'view modes');
|
||||
$pos = $start;
|
||||
$list = ['Breezier', 'Full content'];
|
||||
// Verify that the order of the view modes is correct on the page.
|
||||
foreach ($list as $name) {
|
||||
$new_pos = strpos($page_text, $name, $start);
|
||||
$this->assertGreaterThan($pos, $new_pos);
|
||||
$pos = $new_pos;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the UI for entity displays.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityDisplayTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer entity_test display',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the use of regions for entity view displays.
|
||||
*/
|
||||
public function testEntityView(): void {
|
||||
$this->drupalGet('entity_test/structure/entity_test/display');
|
||||
$this->assertSession()->elementExists('css', '.region-content-message.region-empty');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
|
||||
|
||||
$this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Your settings have been saved.');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests field UI integration with field type categories for loading libraries.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldTypeCategoriesIntegrationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'file',
|
||||
'field_ui',
|
||||
'options',
|
||||
'comment',
|
||||
'link',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser(['administer node fields']);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the libraries are loaded on FieldStorageAddForm.
|
||||
*/
|
||||
public function testLibrariesLoaded(): void {
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->drupalCreateContentType()->id() . '/fields/add-field');
|
||||
$settings = $this->getDrupalSettings();
|
||||
$css_libraries = [
|
||||
'file/drupal.file-icon',
|
||||
'text/drupal.text-icon',
|
||||
'options/drupal.options-icon',
|
||||
'comment/drupal.comment-icon',
|
||||
'link/drupal.link-icon',
|
||||
];
|
||||
$libraries = explode(',', $settings['ajaxPageState']['libraries']);
|
||||
foreach ($css_libraries as $css_library) {
|
||||
$this->assertContains($css_library, $libraries);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\views\Entity\View;
|
||||
use Drupal\views\Tests\ViewTestData;
|
||||
|
||||
/**
|
||||
* Tests deletion of a field and their dependencies in the UI.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldUIDeleteTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'field_test',
|
||||
'block',
|
||||
'field_test_views',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Test views to enable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public static $testViews = ['test_view_field_delete'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'administer node display',
|
||||
'administer users',
|
||||
'administer account settings',
|
||||
'administer user display',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that deletion removes field storages and fields as expected.
|
||||
*/
|
||||
public function testDeleteField(): void {
|
||||
$field_label = $this->randomMachineName();
|
||||
$field_name_input = 'test';
|
||||
$field_name = 'field_test';
|
||||
|
||||
// Create an additional node type.
|
||||
$type_name1 = $this->randomMachineName(8) . '_test';
|
||||
$type1 = $this->drupalCreateContentType(['name' => $type_name1, 'type' => $type_name1]);
|
||||
$type_name1 = $type1->id();
|
||||
|
||||
// Create a new field.
|
||||
$bundle_path1 = 'admin/structure/types/manage/' . $type_name1;
|
||||
$this->fieldUIAddNewField($bundle_path1, $field_name_input, $field_label);
|
||||
|
||||
// Create an additional node type.
|
||||
$type_name2 = $this->randomMachineName(8) . '_test';
|
||||
$type2 = $this->drupalCreateContentType(['name' => $type_name2, 'type' => $type_name2]);
|
||||
$type_name2 = $type2->id();
|
||||
|
||||
// Add a field to the second node type.
|
||||
$bundle_path2 = 'admin/structure/types/manage/' . $type_name2;
|
||||
$this->fieldUIAddExistingField($bundle_path2, $field_name, $field_label);
|
||||
|
||||
\Drupal::service('module_installer')->install(['views']);
|
||||
ViewTestData::createTestViews(static::class, ['field_test_views']);
|
||||
|
||||
$view = View::load('test_view_field_delete');
|
||||
$this->assertNotNull($view);
|
||||
$this->assertTrue($view->status());
|
||||
// Test that the View depends on the field.
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
$this->assertContains("field.storage.node.$field_name", $dependencies['config']);
|
||||
|
||||
// Check the config dependencies of the first field, the field storage must
|
||||
// not be shown as being deleted yet.
|
||||
$this->drupalGet("$bundle_path1/fields/node.$type_name1.$field_name/delete");
|
||||
$this->assertSession()->pageTextNotContains('The listed configuration will be deleted.');
|
||||
$this->assertSession()->elementNotExists('xpath', '//ul[@data-drupal-selector="edit-view"]');
|
||||
$this->assertSession()->pageTextNotContains('test_view_field_delete');
|
||||
// Test Breadcrumbs.
|
||||
$this->assertSession()->linkExists($field_label, 0, 'Field label is correct in the breadcrumb of the field delete page.');
|
||||
|
||||
// Delete the first field.
|
||||
$this->fieldUIDeleteField($bundle_path1, "node.$type_name1.$field_name", $field_label, $type_name1, 'content type');
|
||||
|
||||
// Check that the field was deleted.
|
||||
$this->assertNull(FieldConfig::loadByName('node', $type_name1, $field_name), 'Field was deleted.');
|
||||
// Check that the field storage was not deleted.
|
||||
$this->assertNotNull(FieldStorageConfig::loadByName('node', $field_name), 'Field storage was not deleted.');
|
||||
|
||||
// Check the config dependencies of the first field.
|
||||
$this->drupalGet("$bundle_path2/fields/node.$type_name2.$field_name/delete");
|
||||
$this->assertSession()->pageTextContains('The listed configuration will be updated.');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//ul[@data-drupal-selector="edit-view"]', 'test_view_field_delete');
|
||||
|
||||
// Test that nothing is scheduled for deletion.
|
||||
$this->assertSession()->elementNotExists('css', '#edit-entity-deletes');
|
||||
|
||||
// Delete the second field.
|
||||
$this->fieldUIDeleteField($bundle_path2, "node.$type_name2.$field_name", $field_label, $type_name2, 'content type');
|
||||
|
||||
// Check that the field was deleted.
|
||||
$this->assertNull(FieldConfig::loadByName('node', $type_name2, $field_name), 'Field was deleted.');
|
||||
// Check that the field storage was deleted too.
|
||||
$this->assertNull(FieldStorageConfig::loadByName('node', $field_name), 'Field storage was deleted.');
|
||||
|
||||
// Test that the View isn't deleted and has been disabled.
|
||||
$view = View::load('test_view_field_delete');
|
||||
$this->assertNotNull($view);
|
||||
$this->assertFalse($view->status());
|
||||
// Test that the View no longer depends on the deleted field.
|
||||
$dependencies = $view->getDependencies() + ['config' => []];
|
||||
$this->assertNotContains("field.storage.node.$field_name", $dependencies['config']);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests indentation on Field UI.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldUIIndentationTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['node', 'field_ui', 'field_ui_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create Basic page node type.
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the indentation classes are present in the content type display settings.
|
||||
*/
|
||||
public function testIndentation(): void {
|
||||
$this->drupalGet('admin/structure/types/manage/page/display');
|
||||
$this->assertSession()->responseContains('js-indentation indentation');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Tests the functionality of the Field UI route subscriber.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldUIRouteTest extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'entity_test', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that entity types with bundles do not break following entity types.
|
||||
*/
|
||||
public function testFieldUIRoutes(): void {
|
||||
$route = \Drupal::service('router.route_provider')->getRouteByName('entity.entity_test.field_ui_fields');
|
||||
$is_admin = \Drupal::service('router.admin_context')->isAdminRoute($route);
|
||||
// Asserts that admin routes are correctly marked as such.
|
||||
$this->assertTrue($is_admin, 'Admin route correctly marked for "Manage fields" page.');
|
||||
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'administer account settings',
|
||||
'administer entity_test_no_id fields',
|
||||
'administer user fields',
|
||||
'administer user form display',
|
||||
'administer user display',
|
||||
]));
|
||||
$this->drupalGet('entity_test_no_id/structure/entity_test/fields');
|
||||
$this->assertSession()->pageTextContains('No fields are present yet.');
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/fields');
|
||||
$this->assertSession()->titleEquals('Manage fields | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
// Test manage display tabs and titles.
|
||||
$this->drupalGet('admin/config/people/accounts/display/compact');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/display');
|
||||
$this->assertSession()->titleEquals('Manage display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
$edit = ['display_modes_custom[compact]' => TRUE];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet('admin/config/people/accounts/display/compact');
|
||||
$this->assertSession()->titleEquals('Manage display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
// Test manage form display tabs and titles.
|
||||
$this->drupalGet('admin/config/people/accounts/form-display/register');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
$this->drupalGet('admin/config/people/accounts/form-display');
|
||||
$this->assertSession()->titleEquals('Manage form display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
|
||||
$edit = ['display_modes_custom[register]' => TRUE];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->drupalGet('admin/config/people/accounts/form-display/register');
|
||||
$this->assertSession()->titleEquals('Manage form display | Drupal');
|
||||
$this->assertLocalTasks();
|
||||
// Test that default secondary tab is in first position.
|
||||
$this->assertSession()->elementsCount('xpath', "//ul/li[1]/a[contains(text(), 'Default')]", 1);
|
||||
|
||||
// Create new view mode and verify it's available on the Manage Display
|
||||
// screen after enabling it.
|
||||
EntityViewMode::create([
|
||||
'id' => 'user.test',
|
||||
'label' => 'Test',
|
||||
'targetEntityType' => 'user',
|
||||
])->save();
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
|
||||
$edit = ['display_modes_custom[test]' => TRUE];
|
||||
$this->drupalGet('admin/config/people/accounts/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->linkExists('Test');
|
||||
|
||||
// Create new form mode and verify it's available on the Manage Form
|
||||
// Display screen after enabling it.
|
||||
EntityFormMode::create([
|
||||
'id' => 'user.test',
|
||||
'label' => 'Test',
|
||||
'targetEntityType' => 'user',
|
||||
])->save();
|
||||
$this->container->get('router.builder')->rebuildIfNeeded();
|
||||
|
||||
$edit = ['display_modes_custom[test]' => TRUE];
|
||||
$this->drupalGet('admin/config/people/accounts/form-display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->linkExists('Test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that local tasks exists.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertLocalTasks(): void {
|
||||
$this->assertSession()->linkExists('Settings');
|
||||
$this->assertSession()->linkExists('Manage fields');
|
||||
$this->assertSession()->linkExists('Manage display');
|
||||
$this->assertSession()->linkExists('Manage form display');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for field_ui.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
@ -0,0 +1,448 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Behat\Mink\Exception\ExpectationException;
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\Core\Url;
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage display" and "Manage form display" screens.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageDisplayTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'taxonomy',
|
||||
'search',
|
||||
'field_test',
|
||||
'field_third_party_test',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $type;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private string $vocabulary;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer display modes',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'administer node display',
|
||||
'administer taxonomy',
|
||||
'administer taxonomy_term fields',
|
||||
'administer taxonomy_term form display',
|
||||
'administer taxonomy_term display',
|
||||
'administer users',
|
||||
'administer account settings',
|
||||
'administer user display',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create 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 default vocabulary.
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => $this->randomMachineName(),
|
||||
'description' => $this->randomMachineName(),
|
||||
'vid' => $this->randomMachineName(),
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
'help' => '',
|
||||
'nodes' => ['article' => 'article'],
|
||||
'weight' => mt_rand(0, 10),
|
||||
]);
|
||||
$vocabulary->save();
|
||||
$this->vocabulary = $vocabulary->id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests switching view modes to use custom or 'default' settings'.
|
||||
*/
|
||||
public function testViewModeCustom(): void {
|
||||
// Create a field, and a node with some data for the field.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->type, 'test', 'Test field');
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
// For this test, use a formatter setting value that is an integer unlikely
|
||||
// to appear in a rendered node other than as part of the field being tested
|
||||
// (for example, unlikely to be part of the "Submitted by ... on ..." line).
|
||||
$value = '12345';
|
||||
$settings = [
|
||||
'type' => $this->type,
|
||||
'field_test' => [['value' => $value]],
|
||||
];
|
||||
$node = $this->drupalCreateNode($settings);
|
||||
|
||||
// Gather expected output values with the various formatters.
|
||||
$formatter_plugin_manager = \Drupal::service('plugin.manager.field.formatter');
|
||||
$field_test_default_settings = $formatter_plugin_manager->getDefaultSettings('field_test_default');
|
||||
$field_test_with_prepare_view_settings = $formatter_plugin_manager->getDefaultSettings('field_test_with_prepare_view');
|
||||
$output = [
|
||||
'field_test_default' => $field_test_default_settings['test_formatter_setting'] . '|' . $value,
|
||||
'field_test_with_prepare_view' => $field_test_with_prepare_view_settings['test_formatter_setting_additional'] . '|' . $value . '|' . ($value + 1),
|
||||
];
|
||||
|
||||
// Check that the field is displayed with the default formatter in 'rss'
|
||||
// mode (uses 'default'), and hidden in 'teaser' mode (uses custom
|
||||
// settings).
|
||||
$this->assertNodeViewText($node, 'rss', $output['field_test_default'], "The field is displayed as expected in view modes that use 'default' settings.");
|
||||
$this->assertNodeViewNoText($node, 'teaser', $value, "The field is hidden in view modes that use custom settings.");
|
||||
|
||||
// Change formatter for 'default' mode, check that the field is displayed
|
||||
// accordingly in 'rss' mode.
|
||||
$edit = [
|
||||
'fields[field_test][type]' => 'field_test_with_prepare_view',
|
||||
'fields[field_test][region]' => 'content',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected in view modes that use 'default' settings.");
|
||||
|
||||
// Specialize the 'rss' mode, check that the field is displayed the same.
|
||||
$edit = [
|
||||
"display_modes_custom[rss]" => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected in newly specialized 'rss' mode.");
|
||||
|
||||
// Set the field to 'hidden' in the view mode, check that the field is
|
||||
// hidden.
|
||||
$edit = [
|
||||
'fields[field_test][region]' => 'hidden',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display/rss');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertNodeViewNoText($node, 'rss', $value, "The field is hidden in 'rss' mode.");
|
||||
|
||||
// Set the view mode back to 'default', check that the field is displayed
|
||||
// accordingly.
|
||||
$edit = [
|
||||
"display_modes_custom[rss]" => FALSE,
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertNodeViewText($node, 'rss', $output['field_test_with_prepare_view'], "The field is displayed as expected when 'rss' mode is set back to 'default' settings.");
|
||||
|
||||
// Specialize the view mode again.
|
||||
$edit = [
|
||||
"display_modes_custom[rss]" => TRUE,
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display');
|
||||
$this->submitForm($edit, 'Save');
|
||||
// Check that the previous settings for the view mode have been kept.
|
||||
$this->assertNodeViewNoText($node, 'rss', $value, "The previous settings are kept when 'rss' mode is specialized again.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the local tasks are displayed correctly for view modes.
|
||||
*/
|
||||
public function testViewModeLocalTasks(): void {
|
||||
$manage_display = 'admin/structure/types/manage/' . $this->type . '/display';
|
||||
$this->drupalGet($manage_display);
|
||||
$this->assertSession()->linkNotExists('Full content');
|
||||
$this->assertSession()->linkExists('Teaser');
|
||||
|
||||
$this->drupalGet($manage_display . '/teaser');
|
||||
$this->assertSession()->linkNotExists('Full content');
|
||||
$this->assertSession()->linkExists('Default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that fields with no explicit display settings do not break.
|
||||
*/
|
||||
public function testNonInitializedFields(): void {
|
||||
// Create a test field.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->type, 'test', 'Test');
|
||||
|
||||
// Check that the field appears as 'hidden' on the 'Manage display' page
|
||||
// for the 'teaser' mode.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->type . '/display/teaser');
|
||||
$this->assertSession()->fieldValueEquals('fields[field_test][region]', 'hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests view mode management screens.
|
||||
*/
|
||||
public function testViewModeUi(): void {
|
||||
// Tests table headers on "Manage form" and "Manage display" screens.
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary . '/overview/form-display');
|
||||
$this->assertTableHeaderExistsByLabel('field-display-overview', 'Machine name');
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary . '/overview/display');
|
||||
$this->assertTableHeaderExistsByLabel('field-display-overview', 'Machine name');
|
||||
|
||||
// Tests hiding the view modes fieldset when there's only one available.
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary . '/display');
|
||||
$this->assertSession()->pageTextNotContains('Use custom display settings for the following view modes');
|
||||
|
||||
// This may not trigger a notice when 'view_modes_custom' isn't available.
|
||||
$this->drupalGet('admin/structure/taxonomy/manage/' . $this->vocabulary . '/overview/display');
|
||||
$this->submitForm([], 'Save');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a message is shown when there are no fields.
|
||||
*/
|
||||
public function testNoFieldsDisplayOverview(): void {
|
||||
// Create a fresh content type without any fields.
|
||||
NodeType::create([
|
||||
'type' => 'no_fields',
|
||||
'name' => 'No fields',
|
||||
])->save();
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/no_fields/display');
|
||||
$this->assertSession()->pageTextContains("There are no fields yet added. You can add new fields on the Manage fields page.");
|
||||
$this->assertSession()->linkByHrefExists(Url::fromRoute('entity.node.field_ui_fields', ['node_type' => 'no_fields'])->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if display mode local tasks appear in alphabetical order by label.
|
||||
*/
|
||||
public function testViewModeLocalTasksOrder(): void {
|
||||
$manage_display = 'admin/structure/types/manage/' . $this->type . '/display';
|
||||
|
||||
// Specify the 'rss' mode, check that the field is displayed the same.
|
||||
$edit = [
|
||||
'display_modes_custom[rss]' => TRUE,
|
||||
'display_modes_custom[teaser]' => TRUE,
|
||||
];
|
||||
$this->drupalGet($manage_display);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->assertOrderInPage(['RSS', 'Teaser']);
|
||||
|
||||
$edit = [
|
||||
'label' => 'Breezier',
|
||||
];
|
||||
$this->drupalGet('admin/structure/display-modes/view/manage/node.teaser');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
$this->assertOrderInPage(['Breezier', 'RSS']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if form mode local tasks appear in alphabetical order by label.
|
||||
*/
|
||||
public function testFormModeLocalTasksOrder(): void {
|
||||
EntityFormMode::create([
|
||||
'id' => 'node.big',
|
||||
'label' => 'Big Form',
|
||||
'targetEntityType' => 'node',
|
||||
'description' => 'Test description',
|
||||
])->save();
|
||||
EntityFormMode::create([
|
||||
'id' => 'node.little',
|
||||
'label' => 'Little Form',
|
||||
'targetEntityType' => 'node',
|
||||
])->save();
|
||||
$manage_form = 'admin/structure/types/manage/' . $this->type . '/form-display';
|
||||
$this->drupalGet($manage_form);
|
||||
$this->assertOrderInPage(['Big Form', 'Little Form']);
|
||||
$edit = [
|
||||
'label' => 'Ultimate Form',
|
||||
];
|
||||
$this->drupalGet('admin/structure/display-modes/form/manage/node.big');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->drupalGet($manage_form);
|
||||
$this->assertOrderInPage(['Little Form', 'Ultimate Form']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a string is found in the rendered node in a view mode.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* The node.
|
||||
* @param string $view_mode
|
||||
* The view mode in which the node should be displayed.
|
||||
* @param string $text
|
||||
* Plain text to look for.
|
||||
* @param string $message
|
||||
* Message to display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertNodeViewText(EntityInterface $node, string $view_mode, string $text, string $message): void {
|
||||
$this->assertNodeViewTextHelper($node, $view_mode, $text, $message, FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a string is not found in the rendered node in a view mode.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* The node.
|
||||
* @param string $view_mode
|
||||
* The view mode in which the node should be displayed.
|
||||
* @param string $text
|
||||
* Plain text to look for.
|
||||
* @param string $message
|
||||
* Message to display.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertNodeViewNoText(EntityInterface $node, string $view_mode, string $text, string $message): void {
|
||||
$this->assertNodeViewTextHelper($node, $view_mode, $text, $message, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a string is (not) found in the rendered node in a view mode.
|
||||
*
|
||||
* This helper function is used by assertNodeViewText() and
|
||||
* assertNodeViewNoText().
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $node
|
||||
* The node.
|
||||
* @param string $view_mode
|
||||
* The view mode in which the node should be displayed.
|
||||
* @param string $text
|
||||
* Plain text to look for.
|
||||
* @param string $message
|
||||
* Message to display.
|
||||
* @param bool $not_exists
|
||||
* TRUE if this text should not exist, FALSE if it should.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function assertNodeViewTextHelper(EntityInterface $node, string $view_mode, string $text, string $message, bool $not_exists): void {
|
||||
// Make sure caches on the tester side are refreshed after changes
|
||||
// submitted on the tested side.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
// Render a cloned node, so that we do not alter the original.
|
||||
$clone = clone $node;
|
||||
$element = \Drupal::entityTypeManager()
|
||||
->getViewBuilder('node')
|
||||
->view($clone, $view_mode);
|
||||
$output = (string) \Drupal::service('renderer')->renderRoot($element);
|
||||
|
||||
if ($not_exists) {
|
||||
$this->assertStringNotContainsString((string) $text, $output, $message);
|
||||
}
|
||||
else {
|
||||
$this->assertStringContainsString((string) $text, $output, $message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
$xpath = $this->assertSession()->buildXPathQuery('//select[@name=:name]', [':name' => $name]);
|
||||
$fields = $this->xpath($xpath);
|
||||
if ($fields) {
|
||||
$field = $fields[0];
|
||||
$options = $this->getAllOptionsList($field);
|
||||
|
||||
sort($options);
|
||||
sort($expected_options);
|
||||
|
||||
$this->assertSame($expected_options, $options);
|
||||
}
|
||||
else {
|
||||
$this->fail('Unable to find field ' . $name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts all options from a select element.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $element
|
||||
* The select element field information.
|
||||
*
|
||||
* @return array
|
||||
* An array of option values as strings.
|
||||
*/
|
||||
protected function getAllOptionsList(NodeElement $element): array {
|
||||
$options = [];
|
||||
// Add all options items.
|
||||
foreach ($element->option as $option) {
|
||||
$options[] = $option->getValue();
|
||||
}
|
||||
|
||||
// Loops trough all the option groups
|
||||
foreach ($element->optgroup as $optgroup) {
|
||||
$options = array_merge($this->getAllOptionsList($optgroup), $options);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that several pieces of markup are in a given order in the page.
|
||||
*
|
||||
* @param string[] $items
|
||||
* An ordered list of strings.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ExpectationException
|
||||
* When any of the given string is not found.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @todo Remove this once https://www.drupal.org/node/2817657 is committed.
|
||||
*/
|
||||
protected function assertOrderInPage(array $items): void {
|
||||
$session = $this->getSession();
|
||||
$text = $session->getPage()->getHtml();
|
||||
$strings = [];
|
||||
foreach ($items as $item) {
|
||||
if (($pos = strpos($text, $item)) === FALSE) {
|
||||
throw new ExpectationException("Cannot find '$item' in the page", $session->getDriver());
|
||||
}
|
||||
$strings[$pos] = $item;
|
||||
}
|
||||
ksort($strings);
|
||||
$ordered = implode(', ', array_map(function ($item) {
|
||||
return "'$item'";
|
||||
}, $items));
|
||||
$this->assertSame($items, array_values($strings), "Found strings, ordered as: $ordered.");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,405 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage fields" screen.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageFieldsFunctionalTest extends ManageFieldsFunctionalTestBase {
|
||||
|
||||
/**
|
||||
* Tests that default value is correctly validated and saved.
|
||||
*/
|
||||
public function testDefaultValue(): void {
|
||||
// Create a test field storage and field.
|
||||
$field_name = 'test';
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'test_field',
|
||||
])->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'bundle' => $this->contentType,
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$display_repository->getFormDisplay('node', $this->contentType)
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
|
||||
$admin_path = 'admin/structure/types/manage/' . $this->contentType . '/fields/' . $field->id();
|
||||
$element_id = "edit-default-value-input-$field_name-0-value";
|
||||
$element_name = "default_value_input[{$field_name}][0][value]";
|
||||
$this->drupalGet($admin_path);
|
||||
$this->assertSession()->fieldValueEquals($element_id, '');
|
||||
|
||||
// Check that invalid default values are rejected.
|
||||
$edit = [$element_name => '-1', 'set_default_value' => '1'];
|
||||
$this->drupalGet($admin_path);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("$field_name does not accept the value -1");
|
||||
|
||||
// Check that the default value is saved.
|
||||
$edit = [$element_name => '1', 'set_default_value' => '1'];
|
||||
$this->drupalGet($admin_path);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("Saved $field_name configuration");
|
||||
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
|
||||
$this->assertEquals([['value' => 1]], $field->getDefaultValueLiteral(), 'The default value was correctly saved.');
|
||||
|
||||
// Check that the default value shows up in the form.
|
||||
$this->drupalGet($admin_path);
|
||||
$this->assertSession()->fieldValueEquals($element_id, '1');
|
||||
|
||||
// Check that the default value is left empty when "Set default value"
|
||||
// checkbox is not checked.
|
||||
$edit = [$element_name => '1', 'set_default_value' => '0'];
|
||||
$this->drupalGet($admin_path);
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("Saved $field_name configuration");
|
||||
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
|
||||
$this->assertEquals([], $field->getDefaultValueLiteral(), 'The default value was removed.');
|
||||
|
||||
// Check that the default value can be emptied.
|
||||
$this->drupalGet($admin_path);
|
||||
$edit = [$element_name => ''];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("Saved $field_name configuration");
|
||||
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
|
||||
$this->assertEquals([], $field->getDefaultValueLiteral(), 'The default value was correctly saved.');
|
||||
|
||||
// Check that the default value can be empty when the field is marked as
|
||||
// required and can store unlimited values.
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $field_name);
|
||||
$field_storage->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$field_storage->save();
|
||||
|
||||
$this->drupalGet($admin_path);
|
||||
$edit = [
|
||||
'required' => 1,
|
||||
];
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
$this->drupalGet($admin_path);
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertSession()->pageTextContains("Saved $field_name configuration");
|
||||
$field = FieldConfig::loadByName('node', $this->contentType, $field_name);
|
||||
$this->assertEquals([], $field->getDefaultValueLiteral(), 'The default value was correctly saved.');
|
||||
|
||||
// Check that the default widget is used when the field is hidden.
|
||||
$display_repository->getFormDisplay($field->getTargetEntityTypeId(), $field->getTargetBundle())
|
||||
->removeComponent($field_name)
|
||||
->save();
|
||||
$this->drupalGet($admin_path);
|
||||
$this->assertSession()->fieldValueEquals($element_id, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Field UI respects locked fields.
|
||||
*/
|
||||
public function testLockedField(): void {
|
||||
// Create a locked field and attach it to a bundle. We need to do this
|
||||
// programmatically as there's no way to create a locked field through UI.
|
||||
$field_name = $this->randomMachineName(8);
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => 'test_field',
|
||||
'cardinality' => 1,
|
||||
'locked' => TRUE,
|
||||
]);
|
||||
$field_storage->save();
|
||||
FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => $this->contentType,
|
||||
])->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->contentType)
|
||||
->setComponent($field_name, [
|
||||
'type' => 'test_field_widget',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Check that the links for edit and delete are not present.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
|
||||
$locked = $this->xpath('//tr[@id=:field_name]/td[4]', [':field_name' => $field_name]);
|
||||
$this->assertSame('Locked', $locked[0]->getHtml(), 'Field is marked as Locked in the UI');
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_name . '/delete');
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that Field UI respects the 'no_ui' flag in the field type definition.
|
||||
*/
|
||||
public function testHiddenFields(): void {
|
||||
// Check that the field type is not available in the 'add new field' row.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
|
||||
$this->assertSession()->elementNotExists('xpath', "//a//span[text()='Hidden from UI test field']");
|
||||
$this->assertSession()->elementExists('xpath', "//a//span[text()='Shape']");
|
||||
|
||||
// Create a field storage and a field programmatically.
|
||||
$field_name = 'hidden_test_field';
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'node',
|
||||
'type' => $field_name,
|
||||
])->save();
|
||||
$field = [
|
||||
'field_name' => $field_name,
|
||||
'bundle' => $this->contentType,
|
||||
'entity_type' => 'node',
|
||||
'label' => 'Hidden field',
|
||||
];
|
||||
FieldConfig::create($field)->save();
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', $this->contentType)
|
||||
->setComponent($field_name)
|
||||
->save();
|
||||
$this->assertInstanceOf(FieldConfig::class, FieldConfig::load('node.' . $this->contentType . '.' . $field_name));
|
||||
|
||||
// Check that the newly added field appears on the 'Manage Fields'
|
||||
// screen.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields');
|
||||
$this->assertSession()->elementTextContains('xpath', '//table[@id="field-overview"]//tr[@id="hidden-test-field"]//td[1]', $field['label']);
|
||||
|
||||
// Check that the field does not appear in the 're-use existing field' row
|
||||
// on other bundles.
|
||||
$this->drupalGet('admin/structure/types/manage/page/fields/reuse');
|
||||
$this->assertSession()->elementNotExists('css', ".js-reuse-table [data-field-id='{$field_name}']");
|
||||
$this->assertSession()->elementExists('css', '.js-reuse-table [data-field-id="field_tags"]');
|
||||
|
||||
// Check that non-configurable fields are not available.
|
||||
$field_types = \Drupal::service('plugin.manager.field.field_type')->getDefinitions();
|
||||
foreach ($field_types as $field_type => $definition) {
|
||||
$this->drupalGet('admin/structure/types/manage/page/fields/add-field');
|
||||
$label = (string) $definition['label'];
|
||||
if (empty($definition['no_ui'])) {
|
||||
try {
|
||||
$this->assertSession()->elementExists('xpath', "//a//span[text()='$label']");
|
||||
}
|
||||
catch (ElementNotFoundException) {
|
||||
if ($group = $this->getFieldFromGroup($field_type)) {
|
||||
if ($group !== 'General') {
|
||||
$link = $this->assertSession()->elementExists('xpath', "//a[.//span[text()='$group']]");
|
||||
$link->click();
|
||||
$this->assertSession()
|
||||
->elementExists('css', "[name='field_options_wrapper'][value='$field_type']");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$this->assertSession()->elementNotExists('xpath', "//a//span[text()='$label']");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests validation of duplicate and disallowed field names.
|
||||
*/
|
||||
public function testFieldNameValidation(): void {
|
||||
// field_tags already exists, so we're expecting an error when trying to
|
||||
// create a new field with the same name.
|
||||
$url = 'admin/structure/types/manage/' . $this->contentType . '/fields/add-field';
|
||||
$this->drupalGet($url);
|
||||
$this->clickLink('Boolean');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => $this->randomMachineName(),
|
||||
'field_name' => 'tags',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
|
||||
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
|
||||
|
||||
// Reset the field prefix so we can test properly.
|
||||
$this->config('field_ui.settings')->set('field_prefix', '')->save();
|
||||
|
||||
$label = 'Disallowed field';
|
||||
$edit = [
|
||||
'label' => $label,
|
||||
];
|
||||
|
||||
// Try with an entity key.
|
||||
$edit['field_name'] = 'title';
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
|
||||
$this->drupalGet("{$bundle_path}/fields/add-field");
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$this->submitForm($edit, 'Continue');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
|
||||
|
||||
// Try with a base field.
|
||||
$edit['field_name'] = 'sticky';
|
||||
$bundle_path = 'admin/structure/types/manage/' . $this->contentType;
|
||||
$this->drupalGet("{$bundle_path}/fields/add-field");
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$this->submitForm($edit, 'Continue');
|
||||
$this->assertSession()->pageTextContains('The machine-readable name is already in use. It must be unique.');
|
||||
$this->assertSession()->addressEquals($url . '/test_field/false');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests invalid field UI URLs and destinations.
|
||||
*/
|
||||
public function testInvalidUrlsAndDestinations(): void {
|
||||
$field_id = 'node.foo.bar';
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
|
||||
$this->assertSession()->statusCodeEquals(404);
|
||||
|
||||
$options = [
|
||||
'query' => ['destinations' => ['http://example.com']],
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.body', $options);
|
||||
$this->submitForm([], 'Save settings');
|
||||
// The external redirect should not fire.
|
||||
$this->assertSession()->addressEquals('admin/structure/types/manage/article/fields/node.article.body?destinations%5B0%5D=http%3A//example.com');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->responseContains('Attempt to update field <em class="placeholder">Body</em> failed: <em class="placeholder">The internal path component 'http://example.com' is external. You are not allowed to specify an external URL together with internal:/.</em>.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that help descriptions render valid HTML.
|
||||
*/
|
||||
public function testHelpDescriptions(): void {
|
||||
// Create an image field.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_image',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'image',
|
||||
])->save();
|
||||
|
||||
FieldConfig::create([
|
||||
'field_name' => 'field_image',
|
||||
'entity_type' => 'node',
|
||||
'label' => 'Image',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', 'article')
|
||||
->setComponent('field_image')
|
||||
->save();
|
||||
|
||||
$edit = [
|
||||
'description' => '<strong>Test with an upload field.',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
// Check that hook_field_widget_single_element_form_alter() does believe
|
||||
// this is the default value form.
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_tags');
|
||||
$this->assertSession()->pageTextContains('From hook_field_widget_single_element_form_alter(): Default form is true.');
|
||||
|
||||
$edit = [
|
||||
'description' => '<em>Test with a non upload field.',
|
||||
];
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_tags');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->assertSession()->responseContains('<strong>Test with an upload field.</strong>');
|
||||
$this->assertSession()->responseContains('<em>Test with a non upload field.</em>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the "preconfigured field" functionality.
|
||||
*
|
||||
* @see \Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface
|
||||
*/
|
||||
public function testPreconfiguredFields(): void {
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/add-field');
|
||||
|
||||
// Check that the preconfigured field option exist alongside the regular
|
||||
// field type option.
|
||||
$this->assertSession()->elementExists('xpath', "//a//span[text()='All custom options']");
|
||||
$this->assertSession()->elementExists('xpath', "//a//span[text()='Test field with preconfigured options']");
|
||||
|
||||
// Add a field with every possible preconfigured value.
|
||||
$this->fieldUIAddNewField(NULL, 'test_custom_options', 'Test label', 'field_ui:test_field_with_preconfigured_options:custom_options');
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'field_test_custom_options');
|
||||
$this->assertEquals(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED, $field_storage->getCardinality());
|
||||
$this->assertEquals('preconfigured_storage_setting', $field_storage->getSetting('test_field_storage_setting'));
|
||||
|
||||
$field = FieldConfig::loadByName('node', 'article', 'field_test_custom_options');
|
||||
$this->assertTrue($field->isRequired());
|
||||
$this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$form_display = $display_repository->getFormDisplay('node', 'article');
|
||||
$this->assertEquals('test_field_widget_multiple', $form_display->getComponent('field_test_custom_options')['type']);
|
||||
$view_display = $display_repository->getViewDisplay('node', 'article');
|
||||
$this->assertEquals('field_test_multiple', $view_display->getComponent('field_test_custom_options')['type']);
|
||||
$this->assertEquals('altered dummy test string', $view_display->getComponent('field_test_custom_options')['settings']['test_formatter_setting_multiple']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the 'field_prefix' setting works on Field UI.
|
||||
*/
|
||||
public function testFieldPrefix(): void {
|
||||
// Change default field prefix.
|
||||
$field_prefix = $this->randomMachineName(10);
|
||||
$this->config('field_ui.settings')->set('field_prefix', $field_prefix)->save();
|
||||
|
||||
// Create a field input and label exceeding the new maxlength, which is 22.
|
||||
$field_exceed_max_length_label = $this->randomString(23);
|
||||
$field_exceed_max_length_input = $this->randomMachineName(23);
|
||||
|
||||
// Try to create the field.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$edit = [
|
||||
'label' => $field_exceed_max_length_label,
|
||||
'field_name' => $field_exceed_max_length_input,
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
$this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 22 characters but is currently 23 characters long.');
|
||||
|
||||
// Create a valid field.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/node.' . $this->contentType . '.' . $field_prefix . $this->fieldNameInput);
|
||||
$this->assertSession()->pageTextContains($this->fieldLabel . ' settings for ' . $this->contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translation defaults.
|
||||
*/
|
||||
public function testTranslationDefaults(): void {
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'field_' . $this->fieldNameInput);
|
||||
$this->assertTrue($field_storage->isTranslatable(), 'Field storage translatable.');
|
||||
|
||||
$field = FieldConfig::loadByName('node', $this->contentType, 'field_' . $this->fieldNameInput);
|
||||
$this->assertFalse($field->isTranslatable(), 'Field instance should not be translatable by default.');
|
||||
|
||||
// Add a new field based on an existing field.
|
||||
$this->drupalCreateContentType(['type' => 'additional', 'name' => 'Additional type']);
|
||||
$this->fieldUIAddExistingField("admin/structure/types/manage/additional", $this->fieldName, 'Additional type');
|
||||
|
||||
$field_storage = FieldStorageConfig::loadByName('node', 'field_' . $this->fieldNameInput);
|
||||
$this->assertTrue($field_storage->isTranslatable(), 'Field storage translatable.');
|
||||
|
||||
$field = FieldConfig::loadByName('node', 'additional', 'field_' . $this->fieldNameInput);
|
||||
$this->assertFalse($field->isTranslatable(), 'Field instance should not be translatable by default.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\Tests\node\Traits\NodeAccessTrait;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage fields" screen.
|
||||
*/
|
||||
abstract class ManageFieldsFunctionalTestBase extends BrowserTestBase {
|
||||
|
||||
use EntityReferenceFieldCreationTrait;
|
||||
use FieldUiTestTrait;
|
||||
use NodeAccessTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'field_test',
|
||||
'taxonomy',
|
||||
'image',
|
||||
'block',
|
||||
'node_access_test',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The ID of the custom content type created for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $contentType;
|
||||
|
||||
/**
|
||||
* The label for a random field to be created for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldLabel;
|
||||
|
||||
/**
|
||||
* The input name of a random field to be created for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldNameInput;
|
||||
|
||||
/**
|
||||
* The name of a random field to be created for testing.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $fieldName;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'bypass node access',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'administer node display',
|
||||
'administer taxonomy',
|
||||
'administer taxonomy_term fields',
|
||||
'administer taxonomy_term display',
|
||||
'administer users',
|
||||
'administer account settings',
|
||||
'administer user display',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->contentType = $type->id();
|
||||
|
||||
// Create random field name with markup to test escaping.
|
||||
$this->fieldLabel = '<em>' . $this->randomMachineName(8) . '</em>';
|
||||
$this->fieldNameInput = $this->randomMachineName(8);
|
||||
$this->fieldName = 'field_' . $this->fieldNameInput;
|
||||
|
||||
// Create Basic page and Article node types.
|
||||
$this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
|
||||
$this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
|
||||
|
||||
// Create a vocabulary named "Tags".
|
||||
$vocabulary = Vocabulary::create([
|
||||
'name' => 'Tags',
|
||||
'vid' => 'tags',
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
]);
|
||||
$vocabulary->save();
|
||||
|
||||
// Create a vocabulary named "Kittens".
|
||||
Vocabulary::create([
|
||||
'name' => 'Kittens',
|
||||
'vid' => 'kittens',
|
||||
'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED,
|
||||
])->save();
|
||||
|
||||
$handler_settings = [
|
||||
'target_bundles' => [
|
||||
$vocabulary->id() => $vocabulary->id(),
|
||||
],
|
||||
];
|
||||
$this->createEntityReferenceField('node', 'article', 'field_' . $vocabulary->id(), 'Tags', 'taxonomy_term', 'default', $handler_settings);
|
||||
|
||||
\Drupal::service('entity_display.repository')
|
||||
->getFormDisplay('node', 'article')
|
||||
->setComponent('field_' . $vocabulary->id())
|
||||
->save();
|
||||
|
||||
// Setup node access testing.
|
||||
node_access_rebuild();
|
||||
$this->addPrivateField(NodeType::load('article'));
|
||||
\Drupal::state()->set('node_access_test.private', TRUE);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,333 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage fields" screen.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageFieldsLifecycleTest extends ManageFieldsFunctionalTestBase {
|
||||
|
||||
/**
|
||||
* Runs the field CRUD tests.
|
||||
*
|
||||
* In order to act on the same fields, and not create the fields over and over
|
||||
* again the following tests create, update and delete the same fields.
|
||||
*/
|
||||
public function testCRUDFields(): void {
|
||||
$this->manageFieldsPage();
|
||||
$this->createField();
|
||||
$this->updateField();
|
||||
$this->addExistingField();
|
||||
$this->cardinalitySettings();
|
||||
$this->fieldListAdminPage();
|
||||
$this->deleteField();
|
||||
$this->addPersistentFieldStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the manage fields page.
|
||||
*
|
||||
* @param string $type
|
||||
* (optional) The name of a content type.
|
||||
*/
|
||||
protected function manageFieldsPage($type = ''): void {
|
||||
$type = empty($type) ? $this->contentType : $type;
|
||||
$this->drupalGet('admin/structure/types/manage/' . $type . '/fields');
|
||||
// Check all table columns.
|
||||
$table_headers = ['Label', 'Machine name', 'Field type', 'Operations'];
|
||||
foreach ($table_headers as $table_header) {
|
||||
// We check that the label appear in the table headings.
|
||||
$this->assertSession()->responseContains($table_header . '</th>');
|
||||
}
|
||||
|
||||
// Test the "Create a new field" action link.
|
||||
$this->assertSession()->linkExists('Create a new field');
|
||||
|
||||
// Assert entity operations for all fields.
|
||||
$number_of_links = 2;
|
||||
$number_of_links_found = 0;
|
||||
$operation_links = $this->xpath('//ul[@class = "dropbutton"]/li/a');
|
||||
$url = base_path() . "admin/structure/types/manage/$type/fields/node.$type.body";
|
||||
|
||||
foreach ($operation_links as $link) {
|
||||
switch ($link->getAttribute('title')) {
|
||||
case 'Edit field settings.':
|
||||
$this->assertSame($url, $link->getAttribute('href'));
|
||||
$number_of_links_found++;
|
||||
break;
|
||||
|
||||
case 'Delete field.':
|
||||
$this->assertSame("$url/delete", $link->getAttribute('href'));
|
||||
$number_of_links_found++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertEquals($number_of_links, $number_of_links_found);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a new field.
|
||||
*
|
||||
* @todo Assert properties can be set in the form and read back in
|
||||
* $field_storage and $fields.
|
||||
*/
|
||||
protected function createField(): void {
|
||||
// Create a test field.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $this->contentType, $this->fieldNameInput, $this->fieldLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing an existing field.
|
||||
*/
|
||||
protected function updateField(): void {
|
||||
$field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
|
||||
// Go to the field edit page.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
|
||||
$this->assertSession()->assertEscaped($this->fieldLabel);
|
||||
|
||||
// Populate the field settings with new settings.
|
||||
$string = 'updated dummy test string';
|
||||
$edit = [
|
||||
'settings[test_field_setting]' => $string,
|
||||
'field_storage[subform][settings][test_field_storage_setting]' => $string,
|
||||
];
|
||||
$this->assertSession()->pageTextContains('Default value');
|
||||
$this->submitForm($edit, 'Save settings');
|
||||
|
||||
// Assert the field settings are correct.
|
||||
$this->assertFieldSettings($this->contentType, $this->fieldName, $string);
|
||||
|
||||
// Assert redirection back to the "manage fields" page.
|
||||
$this->assertSession()->addressEquals('admin/structure/types/manage/' . $this->contentType . '/fields');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding an existing field in another content type.
|
||||
*/
|
||||
protected function addExistingField(): void {
|
||||
// Check "Re-use existing field" appears.
|
||||
$this->drupalGet('admin/structure/types/manage/page/fields');
|
||||
$this->assertSession()->pageTextContains('Re-use an existing field');
|
||||
$this->clickLink('Re-use an existing field');
|
||||
// Check that fields of other entity types (here, the 'comment_body' field)
|
||||
// do not show up in the "Re-use existing field" list.
|
||||
$this->assertSession()->elementNotExists('css', '.js-reuse-table [data-field-id="comment_body"]');
|
||||
// Validate the FALSE assertion above by also testing a valid one.
|
||||
$this->assertSession()->elementExists('css', ".js-reuse-table [data-field-id='{$this->fieldName}']");
|
||||
$new_label = $this->fieldLabel . '_2';
|
||||
// Add a new field based on an existing field.
|
||||
$this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $new_label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the cardinality settings of a field.
|
||||
*
|
||||
* We do not test if the number can be submitted with anything else than a
|
||||
* numeric value. That is tested already in FormTest::testNumber().
|
||||
*/
|
||||
protected function cardinalitySettings(): void {
|
||||
$field_edit_path = 'admin/structure/types/manage/article/fields/node.article.body';
|
||||
|
||||
// Assert the cardinality other field cannot be empty when cardinality is
|
||||
// set to 'number'.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => '',
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->pageTextContains('Number of values is required.');
|
||||
|
||||
// Submit a custom number.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 6,
|
||||
];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][cardinality]', 'number');
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][cardinality_number]', 6);
|
||||
|
||||
// Add two entries in the body.
|
||||
$edit = ['title[0][value]' => 'Cardinality', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2'];
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Assert that you can't set the cardinality to a lower number than the
|
||||
// highest delta of this field.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 1,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->pageTextContains("There is 1 entity with 2 or more values in this field");
|
||||
|
||||
// Create a second entity with three values.
|
||||
$edit = ['title[0][value]' => 'Cardinality 3', 'body[0][value]' => 'Body 1', 'body[1][value]' => 'Body 2', 'body[2][value]' => 'Body 3'];
|
||||
$this->drupalGet('node/add/article');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Set to unlimited.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][cardinality]', FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
|
||||
$this->assertSession()->fieldValueEquals('field_storage[subform][cardinality_number]', 1);
|
||||
|
||||
// Assert that you can't set the cardinality to a lower number then the
|
||||
// highest delta of this field but can set it to the same.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 1,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertSession()->pageTextContains("There are 2 entities with 2 or more values in this field");
|
||||
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 2,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->pageTextContains("There is 1 entity with 3 or more values in this field");
|
||||
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 3,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
|
||||
// Test the cardinality validation is not access sensitive.
|
||||
|
||||
// Remove the cardinality limit 4 so we can add a node the user doesn't have
|
||||
// access to.
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => (string) FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$node = $this->drupalCreateNode([
|
||||
'private' => TRUE,
|
||||
'uid' => 0,
|
||||
'type' => 'article',
|
||||
]);
|
||||
$node->body->appendItem('body 1');
|
||||
$node->body->appendItem('body 2');
|
||||
$node->body->appendItem('body 3');
|
||||
$node->body->appendItem('body 4');
|
||||
$node->save();
|
||||
|
||||
// Assert that you can't set the cardinality to a lower number then the
|
||||
// highest delta of this field (including inaccessible entities) but can
|
||||
// set it to the same.
|
||||
$this->drupalGet($field_edit_path);
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 2,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->pageTextContains("There are 2 entities with 3 or more values in this field");
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 3,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->pageTextContains("There is 1 entity with 4 or more values in this field");
|
||||
$edit = [
|
||||
'field_storage[subform][cardinality]' => 'number',
|
||||
'field_storage[subform][cardinality_number]' => 4,
|
||||
];
|
||||
$this->drupalGet($field_edit_path);
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->submitForm([], 'Save settings');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a field from the field edit form.
|
||||
*/
|
||||
protected function deleteField(): void {
|
||||
// Delete the field.
|
||||
$field_id = 'node.' . $this->contentType . '.' . $this->fieldName;
|
||||
$this->drupalGet('admin/structure/types/manage/' . $this->contentType . '/fields/' . $field_id);
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that persistent field storage appears in the field UI.
|
||||
*/
|
||||
protected function addPersistentFieldStorage(): void {
|
||||
$field_storage = FieldStorageConfig::loadByName('node', $this->fieldName);
|
||||
// Persist the field storage even if there are no fields.
|
||||
$field_storage->set('persist_with_no_fields', TRUE)->save();
|
||||
// Delete all instances of the field.
|
||||
foreach ($field_storage->getBundles() as $node_type) {
|
||||
// Delete all the body field instances.
|
||||
$this->drupalGet('admin/structure/types/manage/' . $node_type . '/fields/node.' . $node_type . '.' . $this->fieldName);
|
||||
$this->clickLink('Delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
}
|
||||
// Check "Re-use existing field" appears.
|
||||
$this->drupalGet('admin/structure/types/manage/page/fields');
|
||||
$this->assertSession()->pageTextContains('Re-use an existing field');
|
||||
|
||||
// Ensure that we test with a label that contains HTML.
|
||||
$label = $this->randomMachineName(4) . '<br/>' . $this->randomMachineName(4);
|
||||
// Add a new field for the orphaned storage.
|
||||
$this->fieldUIAddExistingField("admin/structure/types/manage/page", $this->fieldName, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts field settings are as expected.
|
||||
*
|
||||
* @param string $bundle
|
||||
* The bundle name for the field.
|
||||
* @param string $field_name
|
||||
* The field name for the field.
|
||||
* @param string $string
|
||||
* The settings text.
|
||||
* @param string $entity_type
|
||||
* The entity type for the field.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSettings(string $bundle, string $field_name, string $string = 'dummy test string', string $entity_type = 'node'): void {
|
||||
// Assert field storage settings.
|
||||
$field_storage = FieldStorageConfig::loadByName($entity_type, $field_name);
|
||||
$this->assertSame($string, $field_storage->getSetting('test_field_storage_setting'), 'Field storage settings were found.');
|
||||
|
||||
// Assert field settings.
|
||||
$field = FieldConfig::loadByName($entity_type, $bundle, $field_name);
|
||||
$this->assertSame($string, $field->getSetting('test_field_setting'), 'Field settings were found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the field list administration page operates correctly.
|
||||
*/
|
||||
protected function fieldListAdminPage(): void {
|
||||
$this->drupalGet('admin/reports/fields');
|
||||
$this->assertSession()->pageTextContains($this->fieldName);
|
||||
$this->assertSession()->linkByHrefExists('admin/structure/types/manage/' . $this->contentType . '/fields');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage fields" screen.
|
||||
*
|
||||
* @group field_ui
|
||||
* @group #slow
|
||||
*/
|
||||
class ManageFieldsMultipleTypesTest extends ManageFieldsFunctionalTestBase {
|
||||
|
||||
/**
|
||||
* Tests that options are copied over when reusing a field.
|
||||
*
|
||||
* @dataProvider entityTypesProvider
|
||||
*/
|
||||
public function testReuseField($entity_type, $bundle1, $bundle2): void {
|
||||
$field_name = 'test_reuse';
|
||||
$label = $this->randomMachineName();
|
||||
|
||||
// Create field with pre-configured options.
|
||||
$this->drupalGet($bundle1['path'] . "/fields/add-field");
|
||||
$this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
|
||||
$new_label = $this->randomMachineName();
|
||||
$this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
|
||||
$field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
|
||||
$this->assertTrue($field->isRequired());
|
||||
$this->assertEquals($new_label, $field->label());
|
||||
$this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id']);
|
||||
$this->assertEquals('test_field_widget_multiple', $form_display->getComponent("field_{$field_name}")['type']);
|
||||
$view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id']);
|
||||
$this->assertEquals('field_test_multiple', $view_display->getComponent("field_{$field_name}")['type']);
|
||||
$this->assertEquals('altered dummy test string', $view_display->getComponent("field_{$field_name}")['settings']['test_formatter_setting_multiple']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that options are copied over when reusing a field.
|
||||
*
|
||||
* @dataProvider entityTypesProvider
|
||||
*/
|
||||
public function testReuseFieldMultipleDisplay($entity_type, $bundle1, $bundle2): void {
|
||||
// Create additional form mode and enable it on both bundles.
|
||||
EntityFormMode::create([
|
||||
'id' => "{$entity_type}.little",
|
||||
'label' => 'Little Form',
|
||||
'targetEntityType' => $entity_type,
|
||||
])->save();
|
||||
$form_display = EntityFormDisplay::create([
|
||||
'id' => "{$entity_type}.{$bundle1['id']}.little",
|
||||
'targetEntityType' => $entity_type,
|
||||
'status' => TRUE,
|
||||
'bundle' => $bundle1['id'],
|
||||
'mode' => 'little',
|
||||
]);
|
||||
$form_display->save();
|
||||
EntityFormDisplay::create([
|
||||
'id' => "{$entity_type}.{$bundle2['id']}.little",
|
||||
'targetEntityType' => $entity_type,
|
||||
'status' => TRUE,
|
||||
'bundle' => $bundle2['id'],
|
||||
'mode' => 'little',
|
||||
])->save();
|
||||
|
||||
// Create additional view mode and enable it on both bundles.
|
||||
EntityViewMode::create([
|
||||
'id' => "{$entity_type}.little",
|
||||
'targetEntityType' => $entity_type,
|
||||
'status' => TRUE,
|
||||
'enabled' => TRUE,
|
||||
'label' => 'Little View Mode',
|
||||
])->save();
|
||||
$view_display = EntityViewDisplay::create([
|
||||
'id' => "{$entity_type}.{$bundle1['id']}.little",
|
||||
'targetEntityType' => $entity_type,
|
||||
'status' => TRUE,
|
||||
'bundle' => $bundle1['id'],
|
||||
'mode' => 'little',
|
||||
]);
|
||||
$view_display->save();
|
||||
EntityViewDisplay::create([
|
||||
'id' => "{$entity_type}.{$bundle2['id']}.little",
|
||||
'targetEntityType' => $entity_type,
|
||||
'status' => TRUE,
|
||||
'bundle' => $bundle2['id'],
|
||||
'mode' => 'little',
|
||||
])->save();
|
||||
|
||||
$field_name = 'test_reuse';
|
||||
$label = $this->randomMachineName();
|
||||
|
||||
// Create field with pre-configured options.
|
||||
$this->drupalGet($bundle1['path'] . "/fields/add-field");
|
||||
$this->fieldUIAddNewField(NULL, $field_name, $label, 'field_ui:test_field_with_preconfigured_options:custom_options');
|
||||
$view_display->setComponent("field_{$field_name}", [
|
||||
'type' => 'field_test_default',
|
||||
'region' => 'content',
|
||||
])->save();
|
||||
$form_display->setComponent("field_{$field_name}", [
|
||||
'type' => 'test_field_widget',
|
||||
'region' => 'content',
|
||||
])->save();
|
||||
|
||||
$new_label = $this->randomMachineName();
|
||||
$this->fieldUIAddExistingField($bundle2['path'], "field_{$field_name}", $new_label);
|
||||
|
||||
$field = FieldConfig::loadByName($entity_type, $bundle2['id'], "field_{$field_name}");
|
||||
$this->assertTrue($field->isRequired());
|
||||
$this->assertEquals($new_label, $field->label());
|
||||
$this->assertEquals('preconfigured_field_setting', $field->getSetting('test_field_setting'));
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Ensure that the additional form display has correct settings.
|
||||
$form_display = $display_repository->getFormDisplay($entity_type, $bundle2['id'], $form_display->getMode());
|
||||
$this->assertEquals('test_field_widget', $form_display->getComponent("field_{$field_name}")['type']);
|
||||
|
||||
// Ensure that the additional view display has correct settings.
|
||||
$view_display = $display_repository->getViewDisplay($entity_type, $bundle2['id'], $view_display->getMode());
|
||||
$this->assertEquals('field_test_default', $view_display->getComponent("field_{$field_name}")['type']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testing Field UI with multiple entity types.
|
||||
*
|
||||
* @return array
|
||||
* Test cases.
|
||||
*/
|
||||
public static function entityTypesProvider() {
|
||||
return [
|
||||
'node' => [
|
||||
'entity_type' => 'node',
|
||||
'bundle1' => [
|
||||
'id' => 'article',
|
||||
'path' => 'admin/structure/types/manage/article',
|
||||
],
|
||||
'bundle2' => [
|
||||
'id' => 'page',
|
||||
'path' => 'admin/structure/types/manage/page',
|
||||
],
|
||||
],
|
||||
'taxonomy' => [
|
||||
'entity_type' => 'taxonomy_term',
|
||||
'bundle1' => [
|
||||
'id' => 'tags',
|
||||
'path' => 'admin/structure/taxonomy/manage/tags/overview',
|
||||
],
|
||||
'bundle2' => [
|
||||
'id' => 'kittens',
|
||||
'path' => 'admin/structure/taxonomy/manage/kittens/overview',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,372 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Functional;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiTestTrait;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
// cSpell:ignore downlander
|
||||
|
||||
/**
|
||||
* Tests the Manage Display page of a fieldable entity type.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageFieldsTest extends BrowserTestBase {
|
||||
|
||||
use FieldUiTestTrait;
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_test',
|
||||
'field_ui',
|
||||
'field_ui_test',
|
||||
'node',
|
||||
'text',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* A user with permission to administer node fields, etc.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->adminUser = $this->drupalCreateUser(['administer node fields']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->config('system.logging')
|
||||
->set('error_level', ERROR_REPORTING_DISPLAY_ALL)
|
||||
->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests drop button operations on the manage fields page.
|
||||
*/
|
||||
public function testFieldDropButtonOperations(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$bundle = $node_type->id();
|
||||
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('field_storage_config')
|
||||
->create([
|
||||
'type' => 'string',
|
||||
'field_name' => 'highlander',
|
||||
'entity_type' => 'node',
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
$this->container->get('entity_type.manager')
|
||||
->getStorage('field_config')
|
||||
->create([
|
||||
'field_storage' => $storage,
|
||||
'bundle' => $bundle,
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet("/admin/structure/types/manage/{$bundle}/fields");
|
||||
|
||||
// Check that the summary element for the string field type exists and has
|
||||
// the correct text (which comes from the FieldItemBase class).
|
||||
$element = $assert_session->elementExists('css', '#highlander');
|
||||
$summary = $assert_session->elementExists('css', '.field-settings-summary-cell > ul > li', $element);
|
||||
$field_label = $this->container->get('plugin.manager.field.field_type')->getDefinitions()['string']['label'];
|
||||
$this->assertEquals($field_label, $summary->getText());
|
||||
|
||||
// Add an entity reference field, and check that its summary is custom.
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('field_storage_config')
|
||||
->create([
|
||||
'type' => 'entity_reference',
|
||||
'field_name' => 'downlander',
|
||||
'entity_type' => 'node',
|
||||
'settings' => [
|
||||
'target_type' => 'node',
|
||||
],
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
$this->container->get('entity_type.manager')
|
||||
->getStorage('field_config')
|
||||
->create([
|
||||
'field_storage' => $storage,
|
||||
'bundle' => $bundle,
|
||||
'entity_type' => 'node',
|
||||
'settings' => [
|
||||
'handler_settings' => [
|
||||
'target_bundles' => [$bundle => $bundle],
|
||||
],
|
||||
],
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet("/admin/structure/types/manage/{$bundle}/fields");
|
||||
$element = $assert_session->elementExists('css', '#downlander');
|
||||
$custom_summary_text = 'Reference type: Content';
|
||||
$allowed_bundles_text = "Content type: $bundle";
|
||||
$this->assertStringContainsString($custom_summary_text, $element->getText());
|
||||
$this->assertStringContainsString($allowed_bundles_text, $element->getText());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests adding a field.
|
||||
*/
|
||||
public function testAddField(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$type = $this->drupalCreateContentType([
|
||||
'name' => 'Article',
|
||||
'type' => 'article',
|
||||
]);
|
||||
|
||||
// Make sure field descriptions appear, both 1 line and multiple lines.
|
||||
$this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
|
||||
$this->clickLink('Fields for testing descriptions.');
|
||||
$this->assertSession()->pageTextContains('This one-line field description is important for testing');
|
||||
$this->assertSession()->pageTextContains('This multiple line description needs to use an array');
|
||||
$this->assertSession()->pageTextContains('This second line contains important information');
|
||||
|
||||
// Create a new field without actually saving it.
|
||||
$this->fieldUIAddNewField('admin/structure/types/manage/' . $type->id(), 'test_field', 'Test field', 'test_field', [], [], FALSE);
|
||||
// Assert that the field was not created.
|
||||
$this->assertNull(FieldStorageConfig::loadByName('node', "field_test_field"));
|
||||
|
||||
$this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => 'Test field',
|
||||
'field_name' => 'test_field',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
// Test Breadcrumbs.
|
||||
$this->getSession()->getPage()->findLink('Test field');
|
||||
$this->assertSession()->statusMessageNotContains('Saved');
|
||||
|
||||
// Change the storage form values.
|
||||
$edit = ['field_storage[subform][cardinality_number]' => 5];
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->assertSession()->statusMessageNotContains('Saved');
|
||||
|
||||
// Assert that the form values persist.
|
||||
$this->assertEquals(5, $page->findField('field_storage[subform][cardinality_number]')->getValue());
|
||||
|
||||
// Try creating a field with the same machine name.
|
||||
$this->drupalGet('/admin/structure/types/manage/' . $type->id() . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => 'Test field',
|
||||
'field_name' => 'test_field',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
// Assert that the values in the field storage form are reset.
|
||||
$this->assertEquals(1, $page->findField('field_storage[subform][cardinality_number]')->getValue());
|
||||
|
||||
// Assert that the field is created with the new settings.
|
||||
$this->submitForm([], 'Update settings');
|
||||
$this->assertSession()->statusMessageNotContains('Saved');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->statusMessageContains('Saved');
|
||||
|
||||
$this->assertEquals(1, FieldStorageConfig::loadByName('node', 'field_test_field')->getCardinality());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests multiple users adding a field with the same name.
|
||||
*/
|
||||
public function testAddFieldWithMultipleUsers(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
// Create two users.
|
||||
$user1 = $this->drupalCreateUser(['administer node fields']);
|
||||
$user2 = $this->drupalCreateUser(['administer node fields']);
|
||||
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$bundle_path = '/admin/structure/types/manage/' . $node_type->id();
|
||||
|
||||
// Start adding a field as user 1, stop prior to saving, but keep the URL.
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalGet($bundle_path . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => 'Test field',
|
||||
'field_name' => 'test_field',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
// Make changes to the storage form.
|
||||
$edit = ['field_storage[subform][cardinality_number]' => 5];
|
||||
$storage_form_url = $this->getUrl();
|
||||
$this->submitForm($edit, 'Update settings');
|
||||
$this->drupalLogout();
|
||||
|
||||
// Actually add a field as user 2.
|
||||
$this->drupalLogin($user2);
|
||||
$this->drupalGet($bundle_path . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => 'Test field',
|
||||
'field_name' => 'test_field',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
$allowed_no_of_values = $page->findField('field_storage[subform][cardinality_number]')->getValue();
|
||||
// Assert that the changes made by any user do not affect other users until
|
||||
// the field is saved.
|
||||
$this->assertEquals(1, $allowed_no_of_values);
|
||||
$this->submitForm(['field_storage[subform][cardinality_number]' => 2], 'Update settings');
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains("Saved Test field configuration.");
|
||||
$this->drupalLogout();
|
||||
|
||||
// Continue adding a field as user 1, using the URL saved previously.
|
||||
$this->drupalLogin($user1);
|
||||
$this->drupalGet($storage_form_url);
|
||||
// Assert that the user can go on with configuring a field with a machine
|
||||
// that is already taken.
|
||||
$this->assertSession()->pageTextNotContains('error');
|
||||
$this->submitForm([], 'Save');
|
||||
// An error is thrown only after the final 'Save'.
|
||||
$this->assertSession()->statusMessageContains("An error occurred while saving the field: 'field_storage_config' entity with ID 'node.field_test_field' already exists.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing field when the field exists in temp store.
|
||||
*/
|
||||
public function testEditFieldWithLeftOverFieldInTempStore(): void {
|
||||
$user = $this->drupalCreateUser(['administer node fields']);
|
||||
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$bundle_path = '/admin/structure/types/manage/' . $node_type->id();
|
||||
|
||||
// Start adding a field but stop prior to saving.
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet($bundle_path . '/fields/add-field');
|
||||
$this->clickLink('Test field');
|
||||
$this->submitForm([], 'Continue');
|
||||
$edit = [
|
||||
'label' => 'Test field',
|
||||
'field_name' => 'test_field',
|
||||
];
|
||||
$this->submitForm($edit, 'Continue');
|
||||
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('field_storage_config')
|
||||
->create([
|
||||
'type' => 'test_field',
|
||||
'field_name' => 'test_field',
|
||||
'entity_type' => 'node',
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
$this->container->get('entity_type.manager')
|
||||
->getStorage('field_config')
|
||||
->create([
|
||||
'field_storage' => $storage,
|
||||
'bundle' => $node_type->id(),
|
||||
'entity_type' => 'node',
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet("$bundle_path/fields/node.{$node_type->id()}.test_field");
|
||||
$this->submitForm([], 'Save settings');
|
||||
$this->assertSession()->statusMessageContains('Saved test_field configuration.', 'status');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating entity reference field to non-bundleable entity type.
|
||||
*/
|
||||
public function testEntityReferenceToNonBundleableEntity(): void {
|
||||
$type = $this->drupalCreateContentType([
|
||||
'name' => 'kittens',
|
||||
'type' => 'kittens',
|
||||
]);
|
||||
$bundle_path = 'admin/structure/types/manage/' . $type->id();
|
||||
$field_name = 'field_user_reference';
|
||||
|
||||
$field_edit = [
|
||||
'set_default_value' => '1',
|
||||
"default_value_input[$field_name][0][target_id]" => $this->adminUser->label() . ' (' . $this->adminUser->id() . ')',
|
||||
];
|
||||
$this->fieldUIAddNewField($bundle_path, 'user_reference', NULL, 'field_ui:entity_reference:user', [], $field_edit);
|
||||
$field = FieldConfig::loadByName('node', 'kittens', $field_name);
|
||||
$this->assertEquals([['target_id' => $this->adminUser->id()]], $field->getDefaultValue(User::create(['name' => '1337'])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_form_field_storage_config_form_edit_alter().
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testFieldTypeCardinalityAlter(): void {
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$bundle = $node_type->id();
|
||||
|
||||
/** @var \Drupal\field\FieldStorageConfigInterface $storage */
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage('field_storage_config')
|
||||
->create([
|
||||
'type' => 'test_field',
|
||||
'field_name' => 'field_test_field',
|
||||
'entity_type' => 'node',
|
||||
]);
|
||||
$storage->save();
|
||||
|
||||
$this->container->get('entity_type.manager')
|
||||
->getStorage('field_config')
|
||||
->create([
|
||||
'field_storage' => $storage,
|
||||
'bundle' => $bundle,
|
||||
'entity_type' => 'node',
|
||||
])
|
||||
->save();
|
||||
|
||||
$this->drupalGet("/admin/structure/types/manage/$bundle/fields/node.$bundle.field_test_field");
|
||||
$this->assertSession()->elementTextContains('css', '#edit-field-storage', 'Greetings from Drupal\field_test\Plugin\Field\FieldType\TestItem::storageSettingsForm');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests hook_field_info_entity_type_ui_definitions_alter().
|
||||
*/
|
||||
public function testFieldUiDefinitionsAlter(): void {
|
||||
$user = $this->drupalCreateUser(['administer node fields']);
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet('/admin/structure/types/manage/' . $node_type->id() . '/fields/add-field');
|
||||
$this->assertSession()->pageTextContains('Boolean (overridden by alter)');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure field category fallback works for field types without a description.
|
||||
*/
|
||||
public function testFieldCategoryFallbackWithoutDescription(): void {
|
||||
$user = $this->drupalCreateUser(['administer node fields']);
|
||||
$node_type = $this->drupalCreateContentType();
|
||||
$this->drupalLogin($user);
|
||||
$this->drupalGet('/admin/structure/types/manage/' . $node_type->id() . '/fields/add-field');
|
||||
$field_type = $this->assertSession()->elementExists('xpath', '//span[text()="Test field"]');
|
||||
$description_container = $field_type->getParent()->find('css', '.field-option__description');
|
||||
$this->assertNotNull($description_container);
|
||||
$this->assertEquals('', $description_container->getText());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\FunctionalJavascript;
|
||||
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
|
||||
|
||||
/**
|
||||
* Tests the default value widget in Field UI.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class DefaultValueWidgetTest extends WebDriverTestBase {
|
||||
|
||||
use TaxonomyTestTrait;
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'taxonomy',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a Content type and two test nodes.
|
||||
$this->createContentType(['type' => 'test_content']);
|
||||
|
||||
$user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default value options on field config change.
|
||||
*/
|
||||
public function testDefaultValueOptionsForChangingBundles(): void {
|
||||
$vocab_1 = $this->createVocabulary(['name' => 'Colors']);
|
||||
$this->createTerm($vocab_1, ['name' => 'red']);
|
||||
$this->createTerm($vocab_1, ['name' => 'green']);
|
||||
|
||||
$vocab_2 = $this->createVocabulary(['name' => 'Tags']);
|
||||
$this->createTerm($vocab_2, ['name' => 'random tag 1']);
|
||||
$this->createTerm($vocab_2, ['name' => 'random tag 2']);
|
||||
|
||||
$field_name = 'test_field';
|
||||
$this->fieldUIAddNewFieldJS('admin/structure/types/manage/test_content', $field_name, $field_name, 'entity_reference', FALSE);
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->findField('field_storage[subform][settings][target_type]')->selectOption('taxonomy_term');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
$page->findField('settings[handler_settings][target_bundles][' . $vocab_1->id() . ']')->check();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$page->findField('set_default_value')->check();
|
||||
|
||||
$default_value_field = $page->findField('default_value_input[field_' . $field_name . '][0][target_id]');
|
||||
$default_value_field->setValue('r');
|
||||
$this->getSession()->getDriver()->keyDown($default_value_field->getXpath(), ' ');
|
||||
$this->assertSession()->waitOnAutocomplete();
|
||||
|
||||
// Check the autocomplete results.
|
||||
$results = $page->findAll('css', '.ui-autocomplete li');
|
||||
$this->assertCount(2, $results);
|
||||
$this->assertSession()->elementTextNotContains('css', '.ui-autocomplete li', 'random tag 1');
|
||||
$this->assertSession()->elementTextContains('css', '.ui-autocomplete li', 'green');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\FunctionalJavascript;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the bundle selection for view & form display modes.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class DisplayModeBundleSelectionTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalCreateContentType([
|
||||
'name' => 'Article',
|
||||
'type' => 'article',
|
||||
]);
|
||||
$this->drupalCreateContentType([
|
||||
'name' => 'Page',
|
||||
'type' => 'page',
|
||||
]);
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer display modes',
|
||||
'administer node display',
|
||||
'administer node form display',
|
||||
]);
|
||||
// Create a new form mode 'foobar' for content.
|
||||
EntityFormMode::create([
|
||||
'id' => 'node.foobar',
|
||||
'targetEntityType' => 'node',
|
||||
'label' => 'Foobar',
|
||||
])->save();
|
||||
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the bundle selection.
|
||||
*
|
||||
* @param string $display_mode
|
||||
* View or Form display mode.
|
||||
* @param string $path
|
||||
* Display mode path.
|
||||
* @param string $custom_mode
|
||||
* Custom mode to test.
|
||||
*
|
||||
* @dataProvider providerBundleSelection
|
||||
*/
|
||||
public function testBundleSelection($display_mode, $path, $custom_mode): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
// Add new display mode for content.
|
||||
$this->drupalGet("/admin/structure/display-modes/$display_mode");
|
||||
$this->assertNotEmpty($assert_session->waitForText("Add $display_mode mode"));
|
||||
$this->clickLink("Add $display_mode mode for Content");
|
||||
$this->assertNotEmpty($assert_session->waitForText("Add new Content $display_mode mode"));
|
||||
$page->find('css', '[data-drupal-selector="edit-label"]')->setValue('test');
|
||||
$page->find('css', '[data-drupal-selector="edit-bundles-by-entity-article"]')->check();
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Save');
|
||||
|
||||
// Verify that test display mode is selected for article content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/article/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', '[data-drupal-selector="edit-display-modes-custom-test"]');
|
||||
$this->assertTrue($checkbox->isChecked());
|
||||
|
||||
// Verify that test display mode is not selected for page content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/page/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', '[data-drupal-selector="edit-display-modes-custom-test"]');
|
||||
$this->assertFalse($checkbox->isChecked());
|
||||
|
||||
// Click Add view/form display mode button.
|
||||
$this->drupalGet("/admin/structure/display-modes/$display_mode");
|
||||
$this->assertNotEmpty($assert_session->waitForText("Add $display_mode mode"));
|
||||
$this->clickLink("Add $display_mode mode");
|
||||
$this->assertNotEmpty($assert_session->waitForText("Choose $display_mode mode entity type"));
|
||||
|
||||
// Add new view/form display mode for content.
|
||||
$this->clickLink('Content');
|
||||
$this->assertNotEmpty($assert_session->waitForText("Add new Content $display_mode mode"));
|
||||
$page->find('css', '[data-drupal-selector="edit-label"]')->setValue('test2');
|
||||
$page->find('css', '[data-drupal-selector="edit-bundles-by-entity-article"]')->check();
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Save');
|
||||
|
||||
// Verify that test2 display mode is selected for article content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/article/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', '[data-drupal-selector="edit-display-modes-custom-test2"]');
|
||||
$this->assertTrue($checkbox->isChecked());
|
||||
|
||||
// Verify that test2 display mode is not selected for page content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/page/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', '[data-drupal-selector="edit-display-modes-custom-test2"]');
|
||||
$this->assertFalse($checkbox->isChecked());
|
||||
|
||||
// Verify that display mode is not selected on article content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/article/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', "[data-drupal-selector='edit-display-modes-custom-$custom_mode']");
|
||||
$this->assertFalse($checkbox->isChecked());
|
||||
|
||||
// Edit existing display mode and enable it for article content type.
|
||||
$this->drupalGet("/admin/structure/display-modes/$display_mode");
|
||||
$this->assertNotEmpty($assert_session->waitForText("Add $display_mode mode"));
|
||||
$page->find('xpath', '//ul[@class = "dropbutton"]/li[1]/a')->click();
|
||||
$this->assertNotEmpty($assert_session->waitForText("This $display_mode mode will still be available for the rest of the Content types if not checked here, but it will not be enabled by default."));
|
||||
$page->find('css', '[data-drupal-selector="edit-bundles-by-entity-article"]')->check();
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Save');
|
||||
|
||||
// Verify that display mode is selected on article content type.
|
||||
$this->drupalGet("/admin/structure/types/manage/article/$path");
|
||||
$page->find('css', '[data-drupal-selector="edit-modes"]')->pressButton('Custom display settings');
|
||||
$checkbox = $page->find('css', "[data-drupal-selector='edit-display-modes-custom-$custom_mode']");
|
||||
$this->assertTrue($checkbox->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for testBundleSelection().
|
||||
*/
|
||||
public static function providerBundleSelection() {
|
||||
return [
|
||||
'view display' => ['view', 'display', 'full'],
|
||||
'form display' => ['form', 'form-display', 'foobar'],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\FunctionalJavascript;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
|
||||
/**
|
||||
* Tests the UI for entity displays.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityDisplayTest extends WebDriverTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui', 'entity_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'name' => 'The name for this entity',
|
||||
'field_test_text' => [
|
||||
['value' => 'The field test text value'],
|
||||
],
|
||||
]);
|
||||
$entity->save();
|
||||
$this->drupalLogin($this->drupalCreateUser([
|
||||
'access administration pages',
|
||||
'view test entity',
|
||||
'administer entity_test content',
|
||||
'administer entity_test fields',
|
||||
'administer entity_test display',
|
||||
'administer entity_test form display',
|
||||
'view the administration theme',
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the use of regions for entity form displays.
|
||||
*/
|
||||
public function testEntityForm(): void {
|
||||
$this->drupalGet('entity_test/manage/1/edit');
|
||||
$this->assertSession()->fieldExists('field_test_text[0][value]');
|
||||
|
||||
$this->drupalGet('entity_test/structure/entity_test/form-display');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
|
||||
$this->getSession()->getPage()->pressButton('Show row weights');
|
||||
$this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]');
|
||||
$this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'hidden');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Your settings have been saved.');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
|
||||
|
||||
$this->drupalGet('entity_test/manage/1/edit');
|
||||
$this->assertSession()->fieldNotExists('field_test_text[0][value]');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the use of regions for entity view displays.
|
||||
*/
|
||||
public function testEntityView(): void {
|
||||
$this->drupalGet('entity_test/1');
|
||||
$this->assertSession()->pageTextNotContains('The field test text value');
|
||||
|
||||
$this->drupalGet('entity_test/structure/entity_test/display');
|
||||
$this->assertSession()->elementExists('css', '.region-content-message.region-empty');
|
||||
$this->getSession()->getPage()->pressButton('Show row weights');
|
||||
$this->assertSession()->waitForElementVisible('css', '[name="fields[field_test_text][region]"]');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'hidden')->isSelected());
|
||||
|
||||
$this->getSession()->getPage()->selectFieldOption('fields[field_test_text][region]', 'content');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Your settings have been saved.');
|
||||
$this->assertTrue($this->assertSession()->optionExists('fields[field_test_text][region]', 'content')->isSelected());
|
||||
|
||||
$this->drupalGet('entity_test/1');
|
||||
$this->assertSession()->pageTextContains('The field test text value');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests extra fields.
|
||||
*/
|
||||
public function testExtraFields(): void {
|
||||
EntityTestHelper::createBundle('bundle_with_extra_fields');
|
||||
$this->drupalGet('entity_test/structure/bundle_with_extra_fields/display');
|
||||
$this->assertSession()->waitForElement('css', '.tabledrag-handle');
|
||||
$id = $this->getSession()->getPage()->find('css', '[name="form_build_id"]')->getValue();
|
||||
|
||||
$extra_field_row = $this->getSession()->getPage()->find('css', '#display-extra-field');
|
||||
$disabled_region_row = $this->getSession()->getPage()->find('css', '.region-hidden-title');
|
||||
|
||||
$extra_field_row->find('css', '.handle')->dragTo($disabled_region_row);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()
|
||||
->waitForElement('css', "[name='form_build_id']:not([value='$id'])");
|
||||
|
||||
$this->submitForm([], 'Save');
|
||||
$this->assertSession()->pageTextContains('Your settings have been saved.');
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,552 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\FunctionalJavascript;
|
||||
|
||||
use Behat\Mink\Element\NodeElement;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
|
||||
// cspell:ignore onewidgetfield
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage display" and "Manage form display" screens.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageDisplayTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'field_test',
|
||||
'field_third_party_test',
|
||||
'block',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* The content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\entityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The display storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $displayStorage;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
'administer node form display',
|
||||
'administer node display',
|
||||
'administer users',
|
||||
'administer account settings',
|
||||
'administer user display',
|
||||
'bypass node access',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
// Create content type, with underscores.
|
||||
$type_name = $this->randomMachineName(8) . '_test';
|
||||
$type = $this->drupalCreateContentType(['name' => $type_name, 'type' => $type_name]);
|
||||
$this->type = $type->id();
|
||||
|
||||
$this->entityTypeManager = $this->container->get('entity_type.manager');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests formatter settings.
|
||||
*/
|
||||
public function testFormatterUI(): void {
|
||||
$manage_fields = 'admin/structure/types/manage/' . $this->type;
|
||||
$manage_display = $manage_fields . '/display';
|
||||
|
||||
// Create a field, and a node with some data for the field.
|
||||
$this->fieldUIAddNewFieldJS($manage_fields, 'test', 'Test field');
|
||||
|
||||
$display_id = 'node.' . $this->type . '.default';
|
||||
$displayStorage = $this->entityTypeManager->getStorage('entity_view_display');
|
||||
|
||||
// Get the display options (formatter and settings) that were automatically
|
||||
// assigned for the 'default' display.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
|
||||
$display = $displayStorage->loadUnchanged($display_id);
|
||||
$display_options = $display->getComponent('field_test');
|
||||
$format = $display_options['type'];
|
||||
$default_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings($format);
|
||||
$setting_name = key($default_settings);
|
||||
$setting_value = $display_options['settings'][$setting_name];
|
||||
|
||||
// Display the "Manage display" screen and check that the expected formatter
|
||||
// is selected.
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
$session = $this->getSession();
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $session->getPage();
|
||||
|
||||
// Find commonly used elements in this test.
|
||||
$button_save = $page->findButton('Save');
|
||||
$field_test_format_type = $page->findField('fields[field_test][type]');
|
||||
$field_test_drag_handle = $page->find('css', '#field-test .tabledrag-handle');
|
||||
$field_test_settings = $page->find('css', 'input[name="field_test_settings_edit"]');
|
||||
$weight_toggle = $page->find('css', '.tabledrag-toggle-weight');
|
||||
|
||||
// Assert the format type field is visible and contains the expected
|
||||
// formatter.
|
||||
$this->assertTrue($field_test_format_type->isVisible());
|
||||
$this->assertEquals($format, $field_test_format_type->getValue());
|
||||
$assert_session->responseContains("$setting_name: $setting_value");
|
||||
|
||||
// Validate the selectbox.
|
||||
$this->assertFieldSelectOptions($field_test_format_type, [
|
||||
'field_no_settings',
|
||||
'field_empty_test',
|
||||
'field_empty_setting',
|
||||
'field_test_default',
|
||||
'field_test_multiple',
|
||||
'field_test_with_prepare_view',
|
||||
'field_test_applicable',
|
||||
]);
|
||||
|
||||
// Ensure that fields can be hidden directly by dragging the element.
|
||||
$target = $page->find('css', '.region-hidden-message');
|
||||
$field_test_drag_handle->dragTo($target);
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
|
||||
$button_save->click();
|
||||
|
||||
// Validate the changed display settings on the server.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
|
||||
$display = $displayStorage->loadUnchanged($display_id);
|
||||
$this->assertNull($display->getComponent('field_test'));
|
||||
|
||||
// Switch to manual mode.
|
||||
$weight_toggle->click();
|
||||
$field_region = $page->findField('fields[field_test][region]');
|
||||
|
||||
// Change the region to content using the region field.
|
||||
$this->assertEquals('hidden', $field_region->getValue());
|
||||
$field_region->setValue('content');
|
||||
|
||||
// Confirm the region element retains focus after the AJAX update completes.
|
||||
$this->assertJsCondition('document.activeElement === document.querySelector("[name=\'fields[field_test][region]\']")');
|
||||
$button_save->click();
|
||||
|
||||
// Change the format for the test field.
|
||||
$field_test_format_type->setValue('field_test_multiple');
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
|
||||
// Confirm the format element retains focus after the AJAX update completes.
|
||||
$this->assertJsCondition('document.activeElement === document.querySelector("[name=\'fields[field_test][type]\']")');
|
||||
|
||||
$plugin_summary = $page->find('css', '#field-test .field-plugin-summary');
|
||||
$this->assertStringContainsString("test_formatter_setting_multiple: dummy test string", $plugin_summary->getText(), 'The expected summary is displayed.');
|
||||
|
||||
// Submit the form and assert that
|
||||
// hook_field_formatter_settings_summary_alter() is called.
|
||||
$button_save->click();
|
||||
$assert_session->responseContains('field_test_field_formatter_settings_summary_alter');
|
||||
|
||||
// Open the settings form for the test field.
|
||||
$field_test_settings->click();
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
|
||||
// Assert that the field added by the hook
|
||||
// FieldThirdPartyTestHooks::fieldFormatterThirdPartySettingsForm(). is
|
||||
// present. Use an exact match.
|
||||
$field_third_party = $page->find(
|
||||
'named_exact',
|
||||
[
|
||||
'field',
|
||||
'fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][field_test_field_formatter_third_party_settings_form]',
|
||||
]);
|
||||
$this->assertNotEmpty($field_third_party, 'The field added in hook_field_formatter_third_party_settings_form() is present on the settings form.');
|
||||
$this->assertEquals($field_third_party->getAttribute('type'), 'text');
|
||||
|
||||
// Assert that the additional field added in the hook
|
||||
// FieldThirdPartyTestHooks::fieldFormatterThirdPartySettingsFormAdditionalImplementation().
|
||||
// is also present. Use an exact match.
|
||||
// field_formatter_third_party_settings_form
|
||||
// FieldThirdPartyTestHooks::fieldFormatterThirdPartySettingsFormAdditionalImplementation().
|
||||
// is also present. Use exact match.
|
||||
$field_third_party_additional = $page->find(
|
||||
'named_exact',
|
||||
[
|
||||
'field',
|
||||
'fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][second_field_formatter_third_party_settings_form]',
|
||||
]);
|
||||
$this->assertNotEmpty($field_third_party_additional, 'The second field added in hook_field_formatter_third_party_settings_form() is present on the settings form.');
|
||||
$this->assertEquals($field_third_party_additional->getAttribute('type'), 'number');
|
||||
|
||||
// Change the value and submit the form to save the third party settings.
|
||||
$field_third_party->setValue('foo');
|
||||
$page->findButton('Update')->click();
|
||||
$assert_session->assertExpectedAjaxRequest(2);
|
||||
$button_save->click();
|
||||
|
||||
// Assert the third party settings.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
$id = 'node.' . $this->type . '.default';
|
||||
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
|
||||
$display = $displayStorage->loadUnchanged($id);
|
||||
$this->assertEquals('foo', $display->getRenderer('field_test')->getThirdPartySetting('field_third_party_test', 'field_test_field_formatter_third_party_settings_form'));
|
||||
$this->assertContains('field_third_party_test', $display->calculateDependencies()->getDependencies()['module'], 'The display has a dependency on field_third_party_test module.');
|
||||
|
||||
// Change the formatter to an empty setting and validate it's initialized
|
||||
// correctly.
|
||||
$field_test_format_type = $page->findField('fields[field_test][type]');
|
||||
$field_test_format_type->setValue('field_empty_setting');
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$assert_session->responseNotContains('Default empty setting now has a value.');
|
||||
$this->assertTrue($field_test_settings->isVisible());
|
||||
|
||||
// Set the empty_setting option to a non-empty value again and validate
|
||||
// the formatting summary now display's this correctly.
|
||||
$field_test_settings->click();
|
||||
$assert_session->assertExpectedAjaxRequest(2);
|
||||
$field_empty_setting = $page->findField('fields[field_test][settings_edit_form][settings][field_empty_setting]');
|
||||
$field_empty_setting->setValue('non empty setting');
|
||||
$page->findButton('Update')->click();
|
||||
$assert_session->assertExpectedAjaxRequest(3);
|
||||
$assert_session->responseContains('Default empty setting now has a value.');
|
||||
|
||||
// Test the settings form behavior. An edit button should be present since
|
||||
// there are third party settings to configure.
|
||||
$field_test_format_type->setValue('field_no_settings');
|
||||
$this->assertTrue($field_test_settings->isVisible());
|
||||
|
||||
// Make sure we can save the third party settings when there are no settings
|
||||
// available.
|
||||
$field_test_settings->click();
|
||||
$assert_session->assertExpectedAjaxRequest(4);
|
||||
$page->findButton('Update')->click();
|
||||
|
||||
// When a module providing third-party settings to a formatter (or widget)
|
||||
// is uninstalled, the formatter remains enabled but the provided settings,
|
||||
// together with the corresponding form elements, are removed from the
|
||||
// display component.
|
||||
\Drupal::service('module_installer')->uninstall(['field_third_party_test']);
|
||||
|
||||
// Ensure the button is still there after the module has been disabled.
|
||||
$this->drupalGet($manage_display);
|
||||
$this->assertTrue($field_test_settings->isVisible());
|
||||
|
||||
// Ensure that third-party form elements are not present anymore.
|
||||
$field_test_settings->click();
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$field_third_party = $page->findField('fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][field_test_field_formatter_third_party_settings_form]');
|
||||
$this->assertEmpty($field_third_party);
|
||||
|
||||
// Ensure that third-party settings were removed from the formatter.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
|
||||
$display = $displayStorage->loadUnchanged($display_id);
|
||||
$component = $display->getComponent('field_test');
|
||||
$this->assertArrayNotHasKey('field_third_party_test', $component['third_party_settings']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests widget settings.
|
||||
*/
|
||||
public function testWidgetUI(): void {
|
||||
// Admin Manage Fields page.
|
||||
$manage_fields = 'admin/structure/types/manage/' . $this->type;
|
||||
// Admin Manage Display page.
|
||||
$manage_display = $manage_fields . '/form-display';
|
||||
|
||||
$form_storage = $this->entityTypeManager->getStorage('entity_form_display');
|
||||
|
||||
// Creates a new field that can be used with multiple formatters.
|
||||
// Reference: Drupal\field_test\Plugin\Field\FieldWidget\TestFieldWidgetMultiple::isApplicable().
|
||||
$this->fieldUIAddNewFieldJS($manage_fields, 'test', 'Test field');
|
||||
|
||||
// Get the display options (formatter and settings) that were automatically
|
||||
// assigned for the 'default' display.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */
|
||||
$display = $form_storage->loadUnchanged("node.{$this->type}.default");
|
||||
$display_options = $display->getComponent('field_test');
|
||||
$widget_type = $display_options['type'];
|
||||
$default_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings($widget_type);
|
||||
$setting_name = key($default_settings);
|
||||
$setting_value = $display_options['settings'][$setting_name];
|
||||
|
||||
// Display the "Manage form display" screen and check if the expected
|
||||
// widget is selected.
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
$session = $this->getSession();
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $session->getPage();
|
||||
|
||||
$field_test_settings = $page->find('css', 'input[name="field_test_settings_edit"]');
|
||||
$field_test_type = $page->findField('fields[field_test][type]');
|
||||
$button_save = $page->findButton('Save');
|
||||
|
||||
$this->assertEquals($widget_type, $field_test_type->getValue(), 'The expected widget is selected.');
|
||||
$assert_session->responseContains("$setting_name: $setting_value");
|
||||
|
||||
// Check whether widget weights are respected.
|
||||
$this->assertFieldSelectOptions($field_test_type, [
|
||||
'test_field_widget',
|
||||
'test_field_widget_multilingual',
|
||||
'test_field_widget_multiple',
|
||||
]);
|
||||
|
||||
$field_test_type->setValue('test_field_widget_multiple');
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
$button_save->click();
|
||||
|
||||
$this->drupalGet($manage_display);
|
||||
$widget_type = 'test_field_widget_multiple';
|
||||
$default_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings($widget_type);
|
||||
$setting_name = key($default_settings);
|
||||
$setting_value = $default_settings[$setting_name];
|
||||
$this->assertEquals($widget_type, $field_test_type->getValue(), 'The expected widget is selected.');
|
||||
$assert_session->responseContains("$setting_name: $setting_value");
|
||||
$button_save->click();
|
||||
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */
|
||||
$display = $form_storage->loadUnchanged("node.{$this->type}.default");
|
||||
$display_options = $display->getComponent('field_test');
|
||||
$current_widget = $display_options['type'];
|
||||
$current_setting_value = $display_options['settings'][$setting_name];
|
||||
$this->assertEquals($current_widget, $widget_type, 'The widget was updated.');
|
||||
$this->assertEquals($current_setting_value, $setting_value, 'The setting was updated.');
|
||||
|
||||
// Assert that hook_field_widget_settings_summary_alter() is called.
|
||||
$assert_session->responseContains('field_test_field_widget_settings_summary_alter');
|
||||
|
||||
$field_test_settings->click();
|
||||
$assert_session->assertExpectedAjaxRequest(1);
|
||||
|
||||
// Assert that the field added in the hook
|
||||
// FieldThirdPartyTestHooks::fieldWidgetThirdPartySettingsForm().
|
||||
// is present. Use an exact match.
|
||||
$field_third_party_test = $page->find('named_exact',
|
||||
[
|
||||
'field',
|
||||
'fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][field_test_widget_third_party_settings_form]',
|
||||
]);
|
||||
$this->assertNotEmpty($field_third_party_test, 'The field added in hook_field_widget_third_party_settings_form() is present on the settings form.');
|
||||
$this->assertEquals($field_third_party_test->getAttribute('type'), 'text');
|
||||
|
||||
// Assert that the additional field added in the hook
|
||||
// FieldThirdPartyTestHooks::fieldWidgetThirdPartySettingsFormAdditionalImplementation().
|
||||
// is also present.
|
||||
$field_third_party_test_additional = $page->find('named_exact',
|
||||
[
|
||||
'field',
|
||||
'fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][second_field_widget_third_party_settings_form]',
|
||||
]);
|
||||
$this->assertNotEmpty($field_third_party_test_additional, 'The second field added in hook_field_widget_third_party_settings_form() is present on the settings form.');
|
||||
$this->assertEquals($field_third_party_test_additional->getAttribute('type'), 'number');
|
||||
|
||||
$field_third_party_test->setValue('foo');
|
||||
$page->findButton('Update')->click();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
|
||||
$button_save->click();
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
// Assert the third party settings.
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */
|
||||
$display = $form_storage->loadUnchanged('node.' . $this->type . '.default');
|
||||
$this->assertEquals('foo', $display->getRenderer('field_test')->getThirdPartySetting('field_third_party_test', 'field_test_widget_third_party_settings_form'));
|
||||
$this->assertContains('field_third_party_test', $display->calculateDependencies()->getDependencies()['module'], 'Form display does not have a dependency on field_third_party_test module.');
|
||||
|
||||
// Creates a new field that can not be used with the multiple formatter.
|
||||
// Reference: Drupal\field_test\Plugin\Field\FieldWidget\TestFieldWidgetMultiple::isApplicable().
|
||||
$this->fieldUIAddNewFieldJS($manage_fields, 'onewidgetfield', 'One Widget Field');
|
||||
|
||||
// Go to the Manage Form Display.
|
||||
$this->drupalGet($manage_display);
|
||||
|
||||
$field_onewidgetfield_type = $page->findField('fields[field_onewidgetfield][type]');
|
||||
$field_test_drag_handle = $page->find('css', '#field-test .tabledrag-handle');
|
||||
$field_region = $page->findField('fields[field_test][region]');
|
||||
$weight_toggle = $page->find('css', '.tabledrag-toggle-weight');
|
||||
$target = $page->find('css', '.region-hidden-message');
|
||||
|
||||
// Checks if the select elements contain the specified options.
|
||||
$this->assertFieldSelectOptions($field_test_type, [
|
||||
'test_field_widget',
|
||||
'test_field_widget_multilingual',
|
||||
'test_field_widget_multiple',
|
||||
]);
|
||||
$this->assertFieldSelectOptions($field_onewidgetfield_type, [
|
||||
'test_field_widget',
|
||||
'test_field_widget_multilingual',
|
||||
]);
|
||||
|
||||
$field_test_drag_handle->dragTo($target);
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$button_save->click();
|
||||
|
||||
// Validate the changed display settings on the server.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $display */
|
||||
$display = $form_storage->loadUnchanged("node.{$this->type}.default");
|
||||
$this->assertNull($display->getComponent('field_test'));
|
||||
|
||||
// Switch to manual mode.
|
||||
$weight_toggle->click();
|
||||
|
||||
// Change the region to content using the region field.
|
||||
$this->assertEquals('hidden', $field_region->getValue());
|
||||
$field_region->setValue('content');
|
||||
$button_save->click();
|
||||
|
||||
// Validate the change on the server.
|
||||
$this->drupalGet($manage_display);
|
||||
$display = EntityFormDisplay::load("node.{$this->type}.default");
|
||||
$this->assertNotNull($display->getComponent('field_test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a select element contains the specified options.
|
||||
*
|
||||
* @param \Behat\Mink\Element\NodeElement $field
|
||||
* The select field to validate.
|
||||
* @param array $expected_options
|
||||
* An array of expected options.
|
||||
* @param string|null $selected
|
||||
* The default value to validate.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertFieldSelectOptions(NodeElement $field, array $expected_options, ?string $selected = NULL): void {
|
||||
/** @var \Behat\Mink\Element\NodeElement[] $select_options */
|
||||
$select_options = $field->findAll('xpath', 'option');
|
||||
|
||||
// Validate the number of options.
|
||||
$this->assertSameSize($expected_options, $select_options);
|
||||
|
||||
// Validate the options and expected order.
|
||||
foreach ($select_options as $key => $option) {
|
||||
$this->assertEquals($option->getAttribute('value'), $expected_options[$key]);
|
||||
}
|
||||
|
||||
// Validate the default value if passed.
|
||||
if (!is_null($selected)) {
|
||||
$this->assertEquals($selected, $field->getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirms that notifications to save appear when necessary.
|
||||
*/
|
||||
public function testNotAppliedUntilSavedWarning(): void {
|
||||
$assert_session = $this->assertSession();
|
||||
$page = $this->getSession()->getPage();
|
||||
|
||||
// Admin Manage Fields page.
|
||||
$manage_fields = 'admin/structure/types/manage/' . $this->type;
|
||||
|
||||
$this->fieldUIAddNewFieldJS($manage_fields, 'test', 'Test field');
|
||||
$manage_display = 'admin/structure/types/manage/' . $this->type . '/display';
|
||||
$manage_form = 'admin/structure/types/manage/' . $this->type . '/form-display';
|
||||
|
||||
// Form display, change widget type.
|
||||
$this->drupalGet($manage_form);
|
||||
$assert_session->elementNotExists('css', '.tabledrag-changed-warning');
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$page->selectFieldOption('fields[uid][type]', 'options_buttons');
|
||||
$this->assertNotNull($changed_warning = $assert_session->waitForElementVisible('css', '.tabledrag-changed-warning'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', ' #uid abbr.tabledrag-changed'));
|
||||
$this->assertSame('* You have unsaved changes.', $changed_warning->getText());
|
||||
|
||||
// Form display, change widget settings.
|
||||
$this->drupalGet($manage_form);
|
||||
$edit_widget_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-uid-settings-edit"]');
|
||||
$edit_widget_button->press();
|
||||
$assert_session->waitForText('3rd party formatter settings form');
|
||||
|
||||
// Confirm the AJAX operation of opening the form does not result in the row
|
||||
// being set as changed. New settings must be submitted for that to happen.
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$cancel_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-uid-settings-edit-form-actions-cancel-settings"]');
|
||||
$cancel_button->press();
|
||||
$assert_session->assertNoElementAfterWait('css', '[data-drupal-selector="edit-fields-uid-settings-edit-form-actions-cancel-settings"]');
|
||||
$assert_session->elementNotExists('css', '.tabledrag-changed-warning');
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$edit_widget_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-uid-settings-edit"]');
|
||||
$edit_widget_button->press();
|
||||
$widget_field = $assert_session->waitForField('fields[uid][settings_edit_form][third_party_settings][field_third_party_test][field_test_widget_third_party_settings_form]');
|
||||
$widget_field->setValue('honk');
|
||||
$update_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-uid-settings-edit-form-actions-save-settings"]');
|
||||
$update_button->press();
|
||||
$assert_session->assertNoElementAfterWait('css', '[data-drupal-selector="edit-fields-field-test-settings-edit-form-actions-cancel-settings"]');
|
||||
$this->assertNotNull($changed_warning = $assert_session->waitForElementVisible('css', '.tabledrag-changed-warning'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', ' #uid abbr.tabledrag-changed'));
|
||||
$this->assertSame('* You have unsaved changes.', $changed_warning->getText());
|
||||
|
||||
// Content display, change formatter type.
|
||||
$this->drupalGet($manage_display);
|
||||
$assert_session->elementNotExists('css', '.tabledrag-changed-warning');
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$page->selectFieldOption('edit-fields-field-test-label', 'inline');
|
||||
$this->assertNotNull($changed_warning = $assert_session->waitForElementVisible('css', '.tabledrag-changed-warning'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', ' #field-test abbr.tabledrag-changed'));
|
||||
$this->assertSame('* You have unsaved changes.', $changed_warning->getText());
|
||||
|
||||
// Content display, change formatter settings.
|
||||
$this->drupalGet($manage_display);
|
||||
$assert_session->elementNotExists('css', '.tabledrag-changed-warning');
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$edit_formatter_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-field-test-settings-edit"]');
|
||||
$edit_formatter_button->press();
|
||||
$assert_session->waitForText('3rd party formatter settings form');
|
||||
$cancel_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-field-test-settings-edit-form-actions-cancel-settings"]');
|
||||
$cancel_button->press();
|
||||
$assert_session->assertNoElementAfterWait('css', '[data-drupal-selector="edit-fields-field-test-settings-edit-form-actions-cancel-settings"]');
|
||||
$assert_session->elementNotExists('css', '.tabledrag-changed-warning');
|
||||
$assert_session->elementNotExists('css', 'abbr.tabledrag-changed');
|
||||
$edit_formatter_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-field-test-settings-edit"]');
|
||||
$edit_formatter_button->press();
|
||||
$formatter_field = $assert_session->waitForField('fields[field_test][settings_edit_form][third_party_settings][field_third_party_test][field_test_field_formatter_third_party_settings_form]');
|
||||
$formatter_field->setValue('honk');
|
||||
$update_button = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-fields-field-test-settings-edit-form-actions-save-settings"]');
|
||||
$update_button->press();
|
||||
$assert_session->assertNoElementAfterWait('css', '[data-drupal-selector="edit-fields-field-test-settings-edit-form-actions-cancel-settings"]');
|
||||
$this->assertNotNull($changed_warning = $assert_session->waitForElementVisible('css', '.tabledrag-changed-warning'));
|
||||
$this->assertNotNull($assert_session->waitForElementVisible('css', ' #field-test abbr.tabledrag-changed'));
|
||||
$this->assertSame('* You have unsaved changes.', $changed_warning->getText());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,373 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\FunctionalJavascript;
|
||||
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
||||
use Drupal\Tests\field_ui\Traits\FieldUiJSTestTrait;
|
||||
|
||||
// cspell:ignore horserad
|
||||
|
||||
/**
|
||||
* Tests the Field UI "Manage Fields" screens.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class ManageFieldsTest extends WebDriverTestBase {
|
||||
|
||||
use FieldUiJSTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'node',
|
||||
'field_ui',
|
||||
'field_test',
|
||||
'block',
|
||||
'options',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
$this->drupalPlaceBlock('local_tasks_block');
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// Create a test user.
|
||||
$admin_user = $this->drupalCreateUser([
|
||||
'access content',
|
||||
'administer content types',
|
||||
'administer node fields',
|
||||
]);
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
$this->drupalCreateContentType([
|
||||
'name' => 'Article',
|
||||
'type' => 'article',
|
||||
]);
|
||||
|
||||
$this->drupalCreateContentType([
|
||||
'name' => 'Basic Page',
|
||||
'type' => 'page',
|
||||
]);
|
||||
|
||||
$this->getSession()->resizeWindow(1100, 800);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests re-using an existing field and the visibility of the re-use button.
|
||||
*/
|
||||
public function testReuseExistingField(): void {
|
||||
$path = 'admin/structure/types/manage/article';
|
||||
$path2 = 'admin/structure/types/manage/page';
|
||||
$this->drupalGet($path2 . '/fields');
|
||||
// The button should not be visible without any re-usable fields.
|
||||
$this->assertSession()->linkNotExists('Re-use an existing field');
|
||||
$field_label = 'Test field';
|
||||
// Create a field, and a node with some data for the field.
|
||||
$this->fieldUIAddNewFieldJS($path, 'test', $field_label);
|
||||
// Add an existing field.
|
||||
$this->fieldUIAddExistingFieldJS($path2, 'field_test', $field_label);
|
||||
// Confirm the button is no longer visible after re-using the field.
|
||||
$this->assertSession()->linkNotExists('Re-use an existing field');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests filter results in the re-use form.
|
||||
*/
|
||||
public function testFilterInReuseForm(): void {
|
||||
$session = $this->getSession();
|
||||
$page = $session->getPage();
|
||||
$path = 'admin/structure/types/manage/article';
|
||||
$path2 = 'admin/structure/types/manage/page';
|
||||
$this->fieldUIAddNewFieldJS($path, 'horse', 'Horse');
|
||||
$this->fieldUIAddNewFieldJS($path, 'horseradish', 'Horseradish', 'text');
|
||||
$this->fieldUIAddNewFieldJS($path, 'carrot', 'Carrot', 'text');
|
||||
$this->drupalGet($path2 . '/fields');
|
||||
$this->assertSession()->linkExists('Re-use an existing field');
|
||||
$this->clickLink('Re-use an existing field');
|
||||
$this->assertSession()->waitForElementVisible('css', '#drupal-modal');
|
||||
$filter = $this->assertSession()->waitForElementVisible('css', 'input[name="search"]');
|
||||
$horse_field_row = $page->find('css', '.js-reuse-table tr[data-field-id="field_horse"]');
|
||||
$horseradish_field_row = $page->find('css', '.js-reuse-table tr[data-field-id="field_horseradish"]');
|
||||
$carrot_field_row = $page->find('css', '.js-reuse-table tr[data-field-id="field_carrot"]');
|
||||
// Confirm every field is visible first.
|
||||
$this->assertTrue($horse_field_row->isVisible());
|
||||
$this->assertTrue($horseradish_field_row->isVisible());
|
||||
$this->assertTrue($carrot_field_row->isVisible());
|
||||
// Filter by 'horse' field name.
|
||||
$filter->setValue('horse');
|
||||
$session->wait(1000, "jQuery('[data-field-id=\"field_carrot\"]:visible').length == 0");
|
||||
$this->assertTrue($horse_field_row->isVisible());
|
||||
$this->assertTrue($horseradish_field_row->isVisible());
|
||||
$this->assertFalse($carrot_field_row->isVisible());
|
||||
// Filter even more so only 'horseradish' is visible.
|
||||
$filter->setValue('horserad');
|
||||
$session->wait(1000, "jQuery('[data-field-id=\"field_horse\"]:visible').length == 0");
|
||||
$this->assertFalse($horse_field_row->isVisible());
|
||||
$this->assertTrue($horseradish_field_row->isVisible());
|
||||
$this->assertFalse($carrot_field_row->isVisible());
|
||||
// Filter by field type but search with 'ext' instead of 'text' to
|
||||
// confirm that contains-based search works.
|
||||
$filter->setValue('ext');
|
||||
$session->wait(1000, "jQuery('[data-field-id=\"field_horse\"]:visible').length == 0");
|
||||
$session->wait(1000, "jQuery('[data-field-id=\"field_carrot\"]:visible').length == 1");
|
||||
$this->assertFalse($horse_field_row->isVisible());
|
||||
$this->assertTrue($horseradish_field_row->isVisible());
|
||||
$this->assertTrue($carrot_field_row->isVisible());
|
||||
// Ensure clearing brings all the results back.
|
||||
$filter->setValue('');
|
||||
$session->wait(1000, "jQuery('[data-field-id=\"field_horse\"]:visible').length == 1");
|
||||
$this->assertTrue($horse_field_row->isVisible());
|
||||
$this->assertTrue($horseradish_field_row->isVisible());
|
||||
$this->assertTrue($carrot_field_row->isVisible());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that field delete operation opens in modal.
|
||||
*/
|
||||
public function testFieldDelete(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields');
|
||||
|
||||
$page->find('css', '.dropbutton-toggle button')->click();
|
||||
$page->clickLink('Delete');
|
||||
|
||||
// Asserts a dialog opens with the expected text.
|
||||
$this->assertEquals('Are you sure you want to delete the field Body?', $assert_session->waitForElement('css', '.ui-dialog-title')->getText());
|
||||
|
||||
$page->find('css', '.ui-dialog-buttonset')->pressButton('Delete');
|
||||
$assert_session->waitForText('The field Body has been deleted from the Article content type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests field add.
|
||||
*/
|
||||
public function testAddField(): void {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields');
|
||||
|
||||
$this->clickLink('Create a new field');
|
||||
$field_name = 'test_field_1';
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
$this->clickLink('Number');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->waitForText('Add field: Number');
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
// Test validation.
|
||||
$buttons->pressButton('Continue');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextContains('Label field is required.');
|
||||
$assert_session->pageTextContains('You need to select a field type.');
|
||||
$assert_session->elementExists('css', '[name="label"].error');
|
||||
$assert_session->elementExists('css', '[name="field_options_wrapper"].error');
|
||||
$page->fillField('label', $field_name);
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Continue');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
|
||||
$assert_session->pageTextContains('You need to select a field type.');
|
||||
|
||||
$assert_session->elementNotExists('css', '[name="label"].error');
|
||||
$assert_session->elementExists('css', '[name="field_options_wrapper"].error');
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Change field type');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
// Try adding a field using a grouped field type.
|
||||
$this->clickLink('Email');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextNotContains('Choose a field type');
|
||||
$assert_session->elementExists('css', '[name="label"]');
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Change field type');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
$this->clickLink('Plain text');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextContains('Choose a field type');
|
||||
$assert_session->elementExists('css', '[name="label"]');
|
||||
|
||||
$page->fillField('label', $field_name);
|
||||
$this->assertNotEmpty($text_plain = $page->find('xpath', '//*[text() = "Text (plain)"]')->getParent());
|
||||
$text_plain->click();
|
||||
$this->assertTrue($assert_session->elementExists('css', '[name="field_options_wrapper"][value="string"]')->isSelected());
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Continue');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
// Ensure the default value is reloaded when the field storage settings
|
||||
// are changed.
|
||||
$default_input_1_name = 'default_value_input[field_test_field_1][0][value]';
|
||||
$default_input_1 = $assert_session->fieldExists($default_input_1_name);
|
||||
$this->assertFalse($default_input_1->isVisible());
|
||||
|
||||
$default_value = $assert_session->fieldExists('set_default_value');
|
||||
$default_value->check();
|
||||
$assert_session->waitForElementVisible('xpath', $default_value->getXpath());
|
||||
$default_input_1->setValue('There can be only one!');
|
||||
$default_input_2_name = 'default_value_input[field_test_field_1][1][value]';
|
||||
$assert_session->fieldNotExists($default_input_2_name);
|
||||
$cardinality = $assert_session->fieldExists('field_storage[subform][cardinality_number]');
|
||||
$cardinality->setValue(2);
|
||||
$default_input_2 = $assert_session->waitForField($default_input_2_name);
|
||||
// Ensure the default value for first input is retained.
|
||||
$assert_session->fieldValueEquals($default_input_1_name, 'There can be only one!');
|
||||
$page->findField($default_input_2_name)->setValue('But maybe also two?');
|
||||
$cardinality->setValue('1');
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->waitForElementRemoved('xpath', $default_input_2->getXpath());
|
||||
// Ensure the first input retains its value.
|
||||
$assert_session->fieldValueEquals($default_input_1_name, 'There can be only one!');
|
||||
$cardinality->setValue(2);
|
||||
$assert_session->waitForField($default_input_2_name);
|
||||
// Ensure when the second input is added again it does not retain its value.
|
||||
$assert_session->fieldValueEquals($default_input_2_name, '');
|
||||
|
||||
// Ensure changing the max length input will also reload the form.
|
||||
$max_length_input = $assert_session->fieldExists('field_storage[subform][settings][max_length]');
|
||||
$this->assertSame('255', $max_length_input->getValue());
|
||||
$this->assertSame('255', $default_input_1->getAttribute('maxlength'));
|
||||
$max_length_input->setValue('5');
|
||||
$page->waitFor(5, function () use ($default_input_1) {
|
||||
return $default_input_1->getAttribute('maxlength') === '5';
|
||||
});
|
||||
$this->assertSame('5', $default_input_1->getAttribute('maxlength'));
|
||||
// Set a default value that is under the new limit.
|
||||
$default_input_1->setValue('Five!');
|
||||
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Save');
|
||||
$this->assertTrue($assert_session->waitForText('Saved ' . $field_name . ' configuration.'));
|
||||
$this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
|
||||
$this->assertEquals('string', $field_storage->getType());
|
||||
|
||||
// Try adding a field using a non-grouped field type.
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields');
|
||||
$this->clickLink('Create a new field');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($number_field = $page->find('xpath', '//*[text() = "Number"]')->getParent());
|
||||
$number_field->click();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$assert_session->pageTextContains('Choose a field type');
|
||||
|
||||
$this->assertNotEmpty($number_integer = $page->find('xpath', '//*[text() = "Number (integer)"]')->getParent());
|
||||
$number_integer->click();
|
||||
$this->assertTrue($assert_session->elementExists('css', '[name="field_options_wrapper"][value="integer"]')->isSelected());
|
||||
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Change field type');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertNotEmpty($test_field = $page->find('xpath', '//*[text() = "Test field"]')->getParent());
|
||||
$test_field->click();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
$field_name = 'test_field_2';
|
||||
$page->fillField('label', $field_name);
|
||||
$assert_session->pageTextNotContains('Choose a field type');
|
||||
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Continue');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Save');
|
||||
$this->assertTrue($assert_session->waitForText('Saved ' . $field_name . ' configuration.'));
|
||||
$this->assertNotNull($field_storage = FieldStorageConfig::loadByName('node', "field_$field_name"));
|
||||
$this->assertEquals('test_field', $field_storage->getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the order in which the field types appear in the form.
|
||||
*/
|
||||
public function testFieldTypeOrder(): void {
|
||||
$field_type_categories = [
|
||||
'Selection list',
|
||||
'Number',
|
||||
];
|
||||
foreach ($field_type_categories as $field_type_category) {
|
||||
$page = $this->getSession()->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
$this->drupalGet('admin/structure/types/manage/article/fields/add-field');
|
||||
// Select the group card.
|
||||
$this->clickLink($field_type_category);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$field_types = $page->findAll('css', '.subfield-option .option');
|
||||
$field_type_labels = [];
|
||||
foreach ($field_types as $field_type) {
|
||||
$field_type_labels[] = $field_type->getText();
|
||||
}
|
||||
$expected_field_types = match ($field_type_category) {
|
||||
'Selection list' => [
|
||||
'List (text)',
|
||||
'List (integer)',
|
||||
],
|
||||
'Number' => [
|
||||
'Number (integer)',
|
||||
'Number (decimal)',
|
||||
],
|
||||
};
|
||||
// Assert that the field type options are displayed as per their weights.
|
||||
$this->assertSame($expected_field_types, $field_type_labels);
|
||||
// Return to the first step of the form.
|
||||
$assert_session->buttonExists('Change field type')->press();
|
||||
$assert_session->assertWaitOnAjaxRequest();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form validation for allowed values field.
|
||||
*/
|
||||
public function testAllowedValuesFormValidation(): void {
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'type' => 'text',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => 'field_text',
|
||||
'entity_type' => 'node',
|
||||
'bundle' => 'article',
|
||||
])->save();
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields/node.article.field_text');
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->findField('edit-field-storage-subform-cardinality-number')->setValue('-11');
|
||||
$this->assertSession()->assertExpectedAjaxRequest(1);
|
||||
$page->findButton('Save settings')->click();
|
||||
$this->assertSession()->pageTextContains('Limit must be higher than or equal to 1.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the form validation for label field.
|
||||
*/
|
||||
public function testLabelFieldFormValidation(): void {
|
||||
$this->drupalGet('/admin/structure/types/manage/article/fields');
|
||||
$page = $this->getSession()->getPage();
|
||||
$page->clickLink('Create a new field');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->clickLink('Plain text');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$buttons = $this->assertSession()->elementExists('css', '.ui-dialog-buttonpane');
|
||||
$buttons->pressButton('Continue');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->pageTextContains('Label field is required.');
|
||||
$this->assertSession()->pageTextContains('Machine-readable name field is required.');
|
||||
$this->assertSession()->pageTextContains('You need to select a field type.');
|
||||
}
|
||||
|
||||
}
|
||||
722
web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
Normal file
722
web/core/modules/field_ui/tests/src/Kernel/EntityDisplayTest.php
Normal file
@ -0,0 +1,722 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Kernel;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Entity\Display\EntityDisplayInterface;
|
||||
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityViewMode;
|
||||
use Drupal\entity_test\EntityTestHelper;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\user\Entity\Role;
|
||||
|
||||
/**
|
||||
* Tests the entity display configuration entities.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityDisplayTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_ui',
|
||||
'field',
|
||||
'entity_test',
|
||||
'user',
|
||||
'text',
|
||||
'field_test',
|
||||
'node',
|
||||
'system',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('entity_test');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installConfig(['field', 'node', 'user']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests basic CRUD operations on entity display objects.
|
||||
*/
|
||||
public function testEntityDisplayCRUD(): void {
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
$expected = [];
|
||||
|
||||
// Check that providing no 'weight' results in the highest current weight
|
||||
// being assigned. The 'name' field's formatter has weight -5, therefore
|
||||
// these follow.
|
||||
$expected['component_1'] = ['weight' => -4, 'settings' => [], 'third_party_settings' => []];
|
||||
$expected['component_2'] = ['weight' => -3, 'settings' => [], 'third_party_settings' => []];
|
||||
$display->setComponent('component_1');
|
||||
$display->setComponent('component_2');
|
||||
$this->assertEquals($expected['component_1'], $display->getComponent('component_1'));
|
||||
$this->assertEquals($expected['component_2'], $display->getComponent('component_2'));
|
||||
|
||||
// Check that arbitrary options are correctly stored.
|
||||
$expected['component_3'] = [
|
||||
'weight' => 10,
|
||||
'third_party_settings' => ['field_test' => ['foo' => 'bar']],
|
||||
'settings' => [],
|
||||
];
|
||||
$display->setComponent('component_3', $expected['component_3']);
|
||||
$this->assertEquals($expected['component_3'], $display->getComponent('component_3'));
|
||||
|
||||
// Check that the display can be properly saved and read back.
|
||||
$display->save();
|
||||
$display = EntityViewDisplay::load($display->id());
|
||||
foreach (['component_1', 'component_2', 'component_3'] as $name) {
|
||||
$expected[$name]['region'] = 'content';
|
||||
$this->assertEquals($expected[$name], $display->getComponent($name));
|
||||
}
|
||||
|
||||
// Ensure that third party settings were added to the config entity.
|
||||
// These are added by entity_test_entity_presave() implemented in
|
||||
// entity_test module.
|
||||
$this->assertEquals('bar', $display->getThirdPartySetting('entity_test', 'foo'), 'Third party settings were added to the entity view display.');
|
||||
|
||||
// Check that getComponents() returns options for all components.
|
||||
$expected['name'] = [
|
||||
'label' => 'hidden',
|
||||
'type' => 'string',
|
||||
'weight' => -5,
|
||||
'settings' => [
|
||||
'link_to_entity' => FALSE,
|
||||
],
|
||||
'third_party_settings' => [],
|
||||
'region' => 'content',
|
||||
];
|
||||
$this->assertEquals($expected, $display->getComponents());
|
||||
|
||||
// Check that a component can be removed.
|
||||
$display->removeComponent('component_3');
|
||||
$this->assertNULL($display->getComponent('component_3'));
|
||||
|
||||
// Check that the removal is correctly persisted.
|
||||
$display->save();
|
||||
$display = EntityViewDisplay::load($display->id());
|
||||
$this->assertNULL($display->getComponent('component_3'));
|
||||
|
||||
// Check that createCopy() creates a new component that can be correctly
|
||||
// saved.
|
||||
EntityViewMode::create([
|
||||
'id' => $display->getTargetEntityTypeId() . '.other_view_mode',
|
||||
'label' => 'Other',
|
||||
'targetEntityType' => $display->getTargetEntityTypeId(),
|
||||
])->save();
|
||||
$new_display = $display->createCopy('other_view_mode');
|
||||
$new_display->save();
|
||||
$new_display = EntityViewDisplay::load($new_display->id());
|
||||
$dependencies = $new_display->calculateDependencies()->getDependencies();
|
||||
$this->assertEquals(['config' => ['core.entity_view_mode.entity_test.other_view_mode'], 'module' => ['entity_test']], $dependencies);
|
||||
$this->assertEquals($display->getTargetEntityTypeId(), $new_display->getTargetEntityTypeId());
|
||||
$this->assertEquals($display->getTargetBundle(), $new_display->getTargetBundle());
|
||||
$this->assertEquals('other_view_mode', $new_display->getMode());
|
||||
$this->assertEquals($display->getComponents(), $new_display->getComponents());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests sorting of components by name on basic CRUD operations.
|
||||
*/
|
||||
public function testEntityDisplayCRUDSort(): void {
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
$display->setComponent('component_3');
|
||||
$display->setComponent('component_1');
|
||||
$display->setComponent('component_2');
|
||||
$display->save();
|
||||
$components = array_keys($display->getComponents());
|
||||
// The name field is not configurable so will be added automatically.
|
||||
$expected = [0 => 'component_1', 1 => 'component_2', 2 => 'component_3', 'name'];
|
||||
$this->assertSame($expected, $components);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Entity\EntityDisplayRepository::getViewDisplay
|
||||
*/
|
||||
public function testEntityGetDisplay(): void {
|
||||
$display_repository = $this->container->get('entity_display.repository');
|
||||
|
||||
// Check that getViewDisplay() returns a fresh object when no configuration
|
||||
// entry exists.
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertTrue($display->isNew());
|
||||
|
||||
// Add some components and save the display.
|
||||
$display->setComponent('component_1', ['weight' => 10, 'settings' => []])
|
||||
->save();
|
||||
|
||||
// Check that getViewDisplay() returns the correct object.
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertFalse($display->isNew());
|
||||
$this->assertEquals('entity_test.entity_test.default', $display->id());
|
||||
$this->assertEquals(['weight' => 10, 'settings' => [], 'third_party_settings' => [], 'region' => 'content'], $display->getComponent('component_1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field component within an entity display object.
|
||||
*/
|
||||
public function testExtraFieldComponent(): void {
|
||||
EntityTestHelper::createBundle('bundle_with_extra_fields');
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'bundle_with_extra_fields',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
// Check that the default visibility taken into account for extra fields
|
||||
// unknown in the display.
|
||||
$this->assertEquals(['weight' => 5, 'region' => 'content', 'settings' => [], 'third_party_settings' => []], $display->getComponent('display_extra_field'));
|
||||
$this->assertNull($display->getComponent('display_extra_field_hidden'));
|
||||
|
||||
// Check that setting explicit options overrides the defaults.
|
||||
$display->removeComponent('display_extra_field');
|
||||
$display->setComponent('display_extra_field_hidden', ['weight' => 10]);
|
||||
$this->assertNull($display->getComponent('display_extra_field'));
|
||||
$this->assertEquals(['weight' => 10, 'settings' => [], 'third_party_settings' => []], $display->getComponent('display_extra_field_hidden'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of an extra field component with initial invalid values.
|
||||
*/
|
||||
public function testExtraFieldComponentInitialInvalidConfig(): void {
|
||||
EntityTestHelper::createBundle('bundle_with_extra_fields');
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'bundle_with_extra_fields',
|
||||
'mode' => 'default',
|
||||
// Add the extra field to the initial config, without a 'type'.
|
||||
'content' => [
|
||||
'display_extra_field' => [
|
||||
'weight' => 5,
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
// Check that the default visibility taken into account for extra fields
|
||||
// unknown in the display that were included in the initial config.
|
||||
$this->assertEquals(['weight' => 5, 'region' => 'content'], $display->getComponent('display_extra_field'));
|
||||
$this->assertNull($display->getComponent('display_extra_field_hidden'));
|
||||
|
||||
// Check that setting explicit options overrides the defaults.
|
||||
$display->removeComponent('display_extra_field');
|
||||
$display->setComponent('display_extra_field_hidden', ['weight' => 10]);
|
||||
$this->assertNull($display->getComponent('display_extra_field'));
|
||||
$this->assertEquals(['weight' => 10, 'settings' => [], 'third_party_settings' => []], $display->getComponent('display_extra_field_hidden'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field component within an entity display object.
|
||||
*/
|
||||
public function testFieldComponent(): void {
|
||||
$field_name = 'test_field';
|
||||
// Create a field storage and a field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
// Check that providing no options results in default values being used.
|
||||
$display->setComponent($field_name);
|
||||
$field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType());
|
||||
$default_formatter = $field_type_info['default_formatter'];
|
||||
$formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings($default_formatter);
|
||||
$expected = [
|
||||
'weight' => -4,
|
||||
'label' => 'above',
|
||||
'type' => $default_formatter,
|
||||
'settings' => $formatter_settings,
|
||||
'third_party_settings' => [],
|
||||
];
|
||||
$this->assertEquals($expected, $display->getComponent($field_name));
|
||||
|
||||
// Check that the getFormatter() method returns the correct formatter
|
||||
// plugin.
|
||||
$formatter = $display->getRenderer($field_name);
|
||||
$this->assertEquals($default_formatter, $formatter->getPluginId());
|
||||
$this->assertEquals($formatter_settings, $formatter->getSettings());
|
||||
|
||||
// Check that the formatter is statically persisted.
|
||||
$this->assertSame($formatter, $display->getRenderer($field_name));
|
||||
|
||||
// Check that changing the definition creates a new formatter.
|
||||
$display->setComponent($field_name, [
|
||||
'type' => 'field_test_multiple',
|
||||
]);
|
||||
$renderer = $display->getRenderer($field_name);
|
||||
$this->assertEquals('field_test_multiple', $renderer->getPluginId());
|
||||
$this->assertNotSame($formatter, $renderer);
|
||||
|
||||
// Check that the display has dependencies on the field and the module that
|
||||
// provides the formatter.
|
||||
$dependencies = $display->calculateDependencies()->getDependencies();
|
||||
$this->assertEquals(
|
||||
[
|
||||
'config' => ['field.field.entity_test.entity_test.test_field'],
|
||||
'module' => ['entity_test', 'field_test'],
|
||||
],
|
||||
$dependencies
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field component for a base field.
|
||||
*/
|
||||
public function testBaseFieldComponent(): void {
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'entity_test_base_field_display',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
// Check that default options are correctly filled in.
|
||||
$formatter_settings = \Drupal::service('plugin.manager.field.formatter')->getDefaultSettings('text_default');
|
||||
$expected = [
|
||||
'test_no_display' => NULL,
|
||||
'test_display_configurable' => [
|
||||
'label' => 'above',
|
||||
'type' => 'text_default',
|
||||
'settings' => $formatter_settings,
|
||||
'third_party_settings' => [],
|
||||
'weight' => 10,
|
||||
'region' => 'content',
|
||||
],
|
||||
'test_display_non_configurable' => [
|
||||
'label' => 'above',
|
||||
'type' => 'text_default',
|
||||
'settings' => $formatter_settings,
|
||||
'third_party_settings' => [],
|
||||
'weight' => 11,
|
||||
'region' => 'content',
|
||||
],
|
||||
];
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
|
||||
// Check that saving the display only writes data for fields whose display
|
||||
// is configurable.
|
||||
$display->save();
|
||||
$config = $this->config('core.entity_view_display.' . $display->id());
|
||||
$data = $config->get();
|
||||
$this->assertFalse(isset($data['content']['test_no_display']));
|
||||
$this->assertFalse(isset($data['hidden']['test_no_display']));
|
||||
$this->assertEquals($expected['test_display_configurable'], $data['content']['test_display_configurable']);
|
||||
$this->assertFalse(isset($data['content']['test_display_non_configurable']));
|
||||
$this->assertFalse(isset($data['hidden']['test_display_non_configurable']));
|
||||
|
||||
// Check that defaults are correctly filled when loading the display.
|
||||
$display = EntityViewDisplay::load($display->id());
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
|
||||
// Check that data manually written for fields whose display is not
|
||||
// configurable is discarded when loading the display.
|
||||
$data['content']['test_display_non_configurable'] = $expected['test_display_non_configurable'];
|
||||
$data['content']['test_display_non_configurable']['weight']++;
|
||||
$config->setData($data)->save();
|
||||
$display = EntityViewDisplay::load($display->id());
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a bundle.
|
||||
*/
|
||||
public function testDeleteBundle(): void {
|
||||
// Create a node bundle, display and form display object.
|
||||
$type = NodeType::create([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
]);
|
||||
$type->save();
|
||||
node_add_body_field($type);
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
$display_repository->getViewDisplay('node', 'article')->save();
|
||||
$display_repository->getFormDisplay('node', 'article')->save();
|
||||
|
||||
// Delete the bundle.
|
||||
$type->delete();
|
||||
$display = EntityViewDisplay::load('node.article.default');
|
||||
$this->assertFalse((bool) $display);
|
||||
$form_display = EntityFormDisplay::load('node.article.default');
|
||||
$this->assertFalse((bool) $form_display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting field.
|
||||
*/
|
||||
public function testDeleteField(): void {
|
||||
$field_name = 'test_field';
|
||||
// Create a field storage and a field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Create default and teaser entity display.
|
||||
EntityViewMode::create([
|
||||
'id' => 'entity_test.teaser',
|
||||
'label' => 'Teaser',
|
||||
'targetEntityType' => 'entity_test',
|
||||
])->save();
|
||||
EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
])->setComponent($field_name)->save();
|
||||
EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'teaser',
|
||||
])->setComponent($field_name)->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Check the component exists.
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertNotEmpty($display->getComponent($field_name));
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test', 'teaser');
|
||||
$this->assertNotEmpty($display->getComponent($field_name));
|
||||
|
||||
// Delete the field.
|
||||
$field->delete();
|
||||
|
||||
// Check that the component has been removed from the entity displays.
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test', 'teaser');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval().
|
||||
*/
|
||||
public function testOnDependencyRemoval(): void {
|
||||
$this->enableModules(['field_plugins_test']);
|
||||
|
||||
$field_name = 'test_field';
|
||||
// Create a field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'text',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
])->setComponent($field_name, ['type' => 'field_plugins_test_text_formatter'])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Check the component exists and is of the correct type.
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertEquals('field_plugins_test_text_formatter', $display->getComponent($field_name)['type']);
|
||||
|
||||
// Removing the field_plugins_test module should change the component to use
|
||||
// the default formatter for test fields.
|
||||
\Drupal::service('config.manager')->uninstall('module', 'field_plugins_test');
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertEquals('text_default', $display->getComponent($field_name)['type']);
|
||||
|
||||
// Removing the text module should remove the field from the view display.
|
||||
\Drupal::service('config.manager')->uninstall('module', 'text');
|
||||
$display = $display_repository->getViewDisplay('entity_test', 'entity_test');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that entity view display changes invalidates cache tags.
|
||||
*/
|
||||
public function testEntityDisplayInvalidateCacheTags(): void {
|
||||
$cache = \Drupal::cache();
|
||||
$cache->set('cid', 'kittens', Cache::PERMANENT, ['config:entity_view_display_list']);
|
||||
$display = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
$display->setComponent('kitten');
|
||||
$display->save();
|
||||
$this->assertFalse($cache->get('cid'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getDisplayModeOptions().
|
||||
*/
|
||||
public function testGetDisplayModeOptions(): void {
|
||||
NodeType::create([
|
||||
'type' => 'article',
|
||||
'name' => 'Article',
|
||||
])->save();
|
||||
|
||||
EntityViewDisplay::create([
|
||||
'targetEntityType' => 'node',
|
||||
'bundle' => 'article',
|
||||
'mode' => 'default',
|
||||
])->setStatus(TRUE)->save();
|
||||
|
||||
$display_teaser = EntityViewDisplay::create([
|
||||
'targetEntityType' => 'node',
|
||||
'bundle' => 'article',
|
||||
'mode' => 'teaser',
|
||||
]);
|
||||
$display_teaser->save();
|
||||
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
'mode' => 'default',
|
||||
])->setStatus(TRUE)->save();
|
||||
|
||||
$form_display_teaser = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'user',
|
||||
'bundle' => 'user',
|
||||
'mode' => 'register',
|
||||
]);
|
||||
$form_display_teaser->save();
|
||||
|
||||
// Test getViewModeOptionsByBundle().
|
||||
$view_modes = \Drupal::service('entity_display.repository')->getViewModeOptionsByBundle('node', 'article');
|
||||
$this->assertEquals(['default' => 'Default'], $view_modes);
|
||||
$display_teaser->setStatus(TRUE)->save();
|
||||
$view_modes = \Drupal::service('entity_display.repository')->getViewModeOptionsByBundle('node', 'article');
|
||||
$this->assertEquals(['default' => 'Default', 'teaser' => 'Teaser'], $view_modes);
|
||||
|
||||
// Test getFormModeOptionsByBundle().
|
||||
$form_modes = \Drupal::service('entity_display.repository')->getFormModeOptionsByBundle('user', 'user');
|
||||
$this->assertEquals(['default' => 'Default'], $form_modes);
|
||||
$form_display_teaser->setStatus(TRUE)->save();
|
||||
$form_modes = \Drupal::service('entity_display.repository')->getFormModeOptionsByBundle('user', 'user');
|
||||
$this->assertEquals(['default' => 'Default', 'register' => 'Register'], $form_modes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests components dependencies additions.
|
||||
*/
|
||||
public function testComponentDependencies(): void {
|
||||
$this->enableModules(['dblog', 'help']);
|
||||
$this->installSchema('dblog', ['watchdog']);
|
||||
$this->installEntitySchema('user');
|
||||
/** @var \Drupal\user\RoleInterface[] $roles */
|
||||
$roles = [];
|
||||
// Create two arbitrary user roles.
|
||||
for ($i = 0; $i < 2; $i++) {
|
||||
$roles[$i] = Role::create([
|
||||
'id' => $this->randomMachineName(),
|
||||
'label' => $this->randomString(),
|
||||
]);
|
||||
$roles[$i]->save();
|
||||
}
|
||||
|
||||
// Create a field of type 'test_field' attached to 'entity_test'.
|
||||
$field_name = $this->randomMachineName();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
])->save();
|
||||
|
||||
// Create a new form display without components.
|
||||
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
|
||||
$form_display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
$form_display->save();
|
||||
|
||||
$dependencies = ['user.role.' . $roles[0]->id(), 'user.role.' . $roles[1]->id()];
|
||||
|
||||
// The config object should not depend on none of the two $roles.
|
||||
$this->assertNoDependency('config', $dependencies[0], $form_display);
|
||||
$this->assertNoDependency('config', $dependencies[1], $form_display);
|
||||
|
||||
// Add a widget of type 'test_field_widget'.
|
||||
$component = [
|
||||
'type' => 'test_field_widget',
|
||||
'settings' => [
|
||||
'test_widget_setting' => $this->randomString(),
|
||||
'role' => $roles[0]->id(),
|
||||
'role2' => $roles[1]->id(),
|
||||
],
|
||||
'third_party_settings' => [
|
||||
'help' => ['foo' => 'bar'],
|
||||
],
|
||||
];
|
||||
$form_display->setComponent($field_name, $component);
|
||||
$form_display->save();
|
||||
|
||||
// Now, the form display should depend on both user roles $roles.
|
||||
$this->assertDependency('config', $dependencies[0], $form_display);
|
||||
$this->assertDependency('config', $dependencies[1], $form_display);
|
||||
// The form display should depend on 'help' module.
|
||||
$this->assertDependency('module', 'help', $form_display);
|
||||
|
||||
// Delete the first user role entity.
|
||||
$roles[0]->delete();
|
||||
|
||||
// Reload the form display.
|
||||
$form_display = EntityFormDisplay::load($form_display->id());
|
||||
// The display exists.
|
||||
$this->assertNotEmpty($form_display);
|
||||
// The form display should not depend on $role[0] anymore.
|
||||
$this->assertNoDependency('config', $dependencies[0], $form_display);
|
||||
// The form display should depend on 'anonymous' user role.
|
||||
$this->assertDependency('config', 'user.role.anonymous', $form_display);
|
||||
// The form display should depend on 'help' module.
|
||||
$this->assertDependency('module', 'help', $form_display);
|
||||
|
||||
// Manually trigger the removal of configuration belonging to the module
|
||||
// because KernelTestBase::disableModules() is not aware of this.
|
||||
$this->container->get('config.manager')->uninstall('module', 'help');
|
||||
// Uninstall 'help' module.
|
||||
$this->disableModules(['help']);
|
||||
|
||||
// Reload the form display.
|
||||
$form_display = EntityFormDisplay::load($form_display->id());
|
||||
// The display exists.
|
||||
$this->assertNotEmpty($form_display);
|
||||
// The component is still enabled.
|
||||
$this->assertNotNull($form_display->getComponent($field_name));
|
||||
// The form display should not depend on 'help' module anymore.
|
||||
$this->assertNoDependency('module', 'help', $form_display);
|
||||
|
||||
// Delete the 2nd user role entity.
|
||||
$roles[1]->delete();
|
||||
|
||||
// Reload the form display.
|
||||
$form_display = EntityFormDisplay::load($form_display->id());
|
||||
// The display exists.
|
||||
$this->assertNotEmpty($form_display);
|
||||
// The component has been disabled.
|
||||
$this->assertNull($form_display->getComponent($field_name));
|
||||
$this->assertTrue($form_display->get('hidden')[$field_name]);
|
||||
// The correct warning message has been logged.
|
||||
$arguments = ['@display' => 'Entity form display', '@id' => $form_display->id(), '@name' => $field_name];
|
||||
$variables = Database::getConnection()->select('watchdog', 'w')
|
||||
->fields('w', ['variables'])
|
||||
->condition('type', 'system')
|
||||
->condition('message', "@display '@id': Component '@name' was disabled because its settings depend on removed dependencies.")
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertEquals($arguments, unserialize($variables));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that $key is a $type type dependency of $display config entity.
|
||||
*
|
||||
* @param string $type
|
||||
* The dependency type: 'config', 'content', 'module' or 'theme'.
|
||||
* @param string $key
|
||||
* The string to be checked.
|
||||
* @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
|
||||
* The entity display object to get dependencies from.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertDependency(string $type, string $key, EntityDisplayInterface $display): void {
|
||||
$this->assertDependencyHelper(TRUE, $type, $key, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that $key is not a $type type dependency of $display config entity.
|
||||
*
|
||||
* @param string $type
|
||||
* The dependency type: 'config', 'content', 'module' or 'theme'.
|
||||
* @param string $key
|
||||
* The string to be checked.
|
||||
* @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
|
||||
* The entity display object to get dependencies from.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertNoDependency(string $type, string $key, EntityDisplayInterface $display): void {
|
||||
$this->assertDependencyHelper(FALSE, $type, $key, $display);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a helper for dependency assertions.
|
||||
*
|
||||
* @param bool $assertion
|
||||
* Assertion: positive or negative.
|
||||
* @param string $type
|
||||
* The dependency type: 'config', 'content', 'module' or 'theme'.
|
||||
* @param string $key
|
||||
* The string to be checked.
|
||||
* @param \Drupal\Core\Entity\Display\EntityDisplayInterface $display
|
||||
* The entity display object to get dependencies from.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
protected function assertDependencyHelper(bool $assertion, string $type, string $key, EntityDisplayInterface $display): void {
|
||||
$all_dependencies = $display->getDependencies();
|
||||
$dependencies = !empty($all_dependencies[$type]) ? $all_dependencies[$type] : [];
|
||||
$context = $display instanceof EntityViewDisplayInterface ? 'View' : 'Form';
|
||||
$value = $assertion ? in_array($key, $dependencies) : !in_array($key, $dependencies);
|
||||
$display_id = $display->id();
|
||||
$message = $assertion ? "$context display '$display_id' depends on $type '$key'." : "$context display '$display_id' do not depend on $type '$key'.";
|
||||
$this->assertTrue($value, $message);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,311 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Kernel;
|
||||
|
||||
use Drupal\Core\Entity\Entity\EntityFormDisplay;
|
||||
use Drupal\Core\Entity\Entity\EntityFormMode;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the entity display configuration entities.
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class EntityFormDisplayTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'field_ui',
|
||||
'field',
|
||||
'entity_test',
|
||||
'field_test',
|
||||
'system',
|
||||
'text',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('action');
|
||||
$this->installConfig('user');
|
||||
$this->installEntitySchema('entity_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\Core\Entity\EntityDisplayRepository::getFormDisplay
|
||||
*/
|
||||
public function testEntityGetFromDisplay(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Check that EntityDisplayRepositoryInterface::getFormDisplay() returns a
|
||||
// fresh object when no configuration entry exists.
|
||||
$form_display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertTrue($form_display->isNew());
|
||||
|
||||
// Add some components and save the display.
|
||||
$form_display->setComponent('component_1', ['weight' => 10])
|
||||
->save();
|
||||
|
||||
// Check that EntityDisplayRepositoryInterface::getFormDisplay() returns the
|
||||
// correct object.
|
||||
$form_display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertFalse($form_display->isNew());
|
||||
$this->assertEquals('entity_test.entity_test.default', $form_display->id());
|
||||
$this->assertEquals(['weight' => 10, 'settings' => [], 'third_party_settings' => [], 'region' => 'content'], $form_display->getComponent('component_1'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field component within an EntityFormDisplay object.
|
||||
*/
|
||||
public function testFieldComponent(): void {
|
||||
// Create a field storage and a field.
|
||||
$field_name = 'test_field';
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
$form_display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
// Check that providing no options results in default values being used.
|
||||
$form_display->setComponent($field_name);
|
||||
$field_type_info = \Drupal::service('plugin.manager.field.field_type')->getDefinition($field_storage->getType());
|
||||
$default_widget = $field_type_info['default_widget'];
|
||||
$widget_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings($default_widget);
|
||||
$expected = [
|
||||
'weight' => 3,
|
||||
'type' => $default_widget,
|
||||
'settings' => $widget_settings,
|
||||
'third_party_settings' => [],
|
||||
];
|
||||
$this->assertEquals($expected, $form_display->getComponent($field_name));
|
||||
|
||||
// Check that the getWidget() method returns the correct widget plugin.
|
||||
$widget = $form_display->getRenderer($field_name);
|
||||
$this->assertEquals($default_widget, $widget->getPluginId());
|
||||
$this->assertEquals($widget_settings, $widget->getSettings());
|
||||
|
||||
// Check that the widget is statically persisted.
|
||||
$this->assertSame($widget, $form_display->getRenderer($field_name));
|
||||
|
||||
// Check that changing the definition creates a new widget.
|
||||
$form_display->setComponent($field_name, [
|
||||
'type' => 'field_test_multiple',
|
||||
]);
|
||||
$renderer = $form_display->getRenderer($field_name);
|
||||
$this->assertEquals('test_field_widget', $renderer->getPluginId());
|
||||
$this->assertNotSame($widget, $renderer);
|
||||
|
||||
// Check that specifying an unknown widget (e.g. case of a disabled module)
|
||||
// gets stored as is in the display, but results in the default widget being
|
||||
// used.
|
||||
$form_display->setComponent($field_name, [
|
||||
'type' => 'unknown_widget',
|
||||
]);
|
||||
$options = $form_display->getComponent($field_name);
|
||||
$this->assertEquals('unknown_widget', $options['type']);
|
||||
$widget = $form_display->getRenderer($field_name);
|
||||
$this->assertEquals($default_widget, $widget->getPluginId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the behavior of a field component for a base field.
|
||||
*/
|
||||
public function testBaseFieldComponent(): void {
|
||||
$display = EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test_base_field_display',
|
||||
'bundle' => 'entity_test_base_field_display',
|
||||
'mode' => 'default',
|
||||
]);
|
||||
|
||||
// Check that default options are correctly filled in.
|
||||
$formatter_settings = \Drupal::service('plugin.manager.field.widget')->getDefaultSettings('text_textfield');
|
||||
$expected = [
|
||||
'test_no_display' => NULL,
|
||||
'test_display_configurable' => [
|
||||
'type' => 'text_textfield',
|
||||
'settings' => $formatter_settings,
|
||||
'third_party_settings' => [],
|
||||
'weight' => 10,
|
||||
'region' => 'content',
|
||||
],
|
||||
'test_display_non_configurable' => [
|
||||
'type' => 'text_textfield',
|
||||
'settings' => $formatter_settings,
|
||||
'third_party_settings' => [],
|
||||
'weight' => 11,
|
||||
'region' => 'content',
|
||||
],
|
||||
];
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
|
||||
// Check that saving the display only writes data for fields whose display
|
||||
// is configurable.
|
||||
$display->save();
|
||||
$config = $this->config('core.entity_form_display.' . $display->id());
|
||||
$data = $config->get();
|
||||
$this->assertFalse(isset($data['content']['test_no_display']));
|
||||
$this->assertFalse(isset($data['hidden']['test_no_display']));
|
||||
$this->assertEquals($expected['test_display_configurable'], $data['content']['test_display_configurable']);
|
||||
$this->assertFalse(isset($data['content']['test_display_non_configurable']));
|
||||
$this->assertFalse(isset($data['hidden']['test_display_non_configurable']));
|
||||
|
||||
// Check that defaults are correctly filled when loading the display.
|
||||
$display = EntityFormDisplay::load($display->id());
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
|
||||
// Check that data manually written for fields whose display is not
|
||||
// configurable is discarded when loading the display.
|
||||
$data['content']['test_display_non_configurable'] = $expected['test_display_non_configurable'];
|
||||
$data['content']['test_display_non_configurable']['weight']++;
|
||||
$config->setData($data)->save();
|
||||
$display = EntityFormDisplay::load($display->id());
|
||||
foreach ($expected as $field_name => $options) {
|
||||
$this->assertEquals($options, $display->getComponent($field_name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting field.
|
||||
*/
|
||||
public function testDeleteField(): void {
|
||||
$field_name = 'test_field';
|
||||
// Create a field storage and a field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'test_field',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
// Create default and compact entity display.
|
||||
EntityFormMode::create([
|
||||
'id' => 'entity_test.compact',
|
||||
'label' => 'Compact',
|
||||
'targetEntityType' => 'entity_test',
|
||||
])->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
])->setComponent($field_name)->save();
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'compact',
|
||||
])->setComponent($field_name)->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Check the component exists.
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertNotEmpty($display->getComponent($field_name));
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test', 'compact');
|
||||
$this->assertNotEmpty($display->getComponent($field_name));
|
||||
|
||||
// Delete the field.
|
||||
$field->delete();
|
||||
|
||||
// Check that the component has been removed from the entity displays.
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test', 'compact');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests \Drupal\Core\Entity\EntityDisplayBase::onDependencyRemoval().
|
||||
*/
|
||||
public function testOnDependencyRemoval(): void {
|
||||
$this->enableModules(['field_plugins_test']);
|
||||
|
||||
$field_name = 'test_field';
|
||||
// Create a field.
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => $field_name,
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'text',
|
||||
]);
|
||||
$field_storage->save();
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => $field_storage,
|
||||
'bundle' => 'entity_test',
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
EntityFormDisplay::create([
|
||||
'targetEntityType' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'mode' => 'default',
|
||||
])->setComponent($field_name, ['type' => 'field_plugins_test_text_widget'])->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Check the component exists and is of the correct type.
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertEquals('field_plugins_test_text_widget', $display->getComponent($field_name)['type']);
|
||||
|
||||
// Removing the field_plugins_test module should change the component to use
|
||||
// the default widget for test fields.
|
||||
\Drupal::service('config.manager')->uninstall('module', 'field_plugins_test');
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertEquals('text_textfield', $display->getComponent($field_name)['type']);
|
||||
|
||||
// Removing the text module should remove the field from the form display.
|
||||
\Drupal::service('config.manager')->uninstall('module', 'text');
|
||||
$display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
$this->assertNull($display->getComponent($field_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the serialization and unserialization of the class.
|
||||
*/
|
||||
public function testSerialization(): void {
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
$form_display = $display_repository->getFormDisplay('entity_test', 'entity_test');
|
||||
// Make sure the langcode base field is visible in the original form
|
||||
// display.
|
||||
$this->assertNotEmpty($form_display->getComponent('langcode'));
|
||||
// Remove the langcode.
|
||||
$form_display->removeComponent('langcode');
|
||||
|
||||
$unserialized = unserialize(serialize($form_display));
|
||||
// Verify that components are retained upon unserialization.
|
||||
$this->assertEquals($form_display->getComponents(), $unserialized->getComponents());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Traits;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Provides common functionality for the Field UI tests that depend on JS.
|
||||
*/
|
||||
trait FieldUiJSTestTrait {
|
||||
use FieldUiTestTrait;
|
||||
|
||||
/**
|
||||
* Creates a new field through the Field UI.
|
||||
*
|
||||
* @param string|null $bundle_path
|
||||
* Admin path of the bundle that the new field is to be attached to.
|
||||
* @param string $field_name
|
||||
* The field name of the new field storage.
|
||||
* @param string|null $label
|
||||
* (optional) The label of the new field. Defaults to a random string.
|
||||
* @param string $field_type
|
||||
* (optional) The field type of the new field storage. Defaults to
|
||||
* 'test_field'.
|
||||
* @param bool $save_settings
|
||||
* (optional) Parameter for conditional execution of second and third step
|
||||
* (Saving the storage settings and field settings). Defaults to 'TRUE'.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
*/
|
||||
public function fieldUIAddNewFieldJS(?string $bundle_path, string $field_name, ?string $label = NULL, string $field_type = 'test_field', bool $save_settings = TRUE): void {
|
||||
$this->getSession()->resizeWindow(1200, 800);
|
||||
$label = $label ?: $field_name;
|
||||
|
||||
// Allow the caller to set a NULL path in case they navigated to the right
|
||||
// page before calling this method.
|
||||
if ($bundle_path !== NULL) {
|
||||
$bundle_path = "$bundle_path/fields";
|
||||
$this->drupalGet($bundle_path);
|
||||
$this->getSession()->getPage()->clickLink('Create a new field');
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
}
|
||||
|
||||
// First step: 'Add field' page.
|
||||
$session = $this->getSession();
|
||||
|
||||
$page = $session->getPage();
|
||||
$assert_session = $this->assertSession();
|
||||
|
||||
try {
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */
|
||||
$field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$field_definitions = $field_type_plugin_manager->getUiDefinitions();
|
||||
$field_type_label = (string) $field_definitions[$field_type]['label'];
|
||||
$this->getSession()->getPage()->clickLink($field_type_label);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
|
||||
if ($this->getSession()->getPage()->hasField('field_options_wrapper')) {
|
||||
$this->assertSession()->fieldExists('field_options_wrapper')->selectOption($field_type);
|
||||
}
|
||||
}
|
||||
// If the element could not be found then it is probably in a group.
|
||||
catch (ElementNotFoundException) {
|
||||
// Call the helper function to confirm it is in a group.
|
||||
$field_group = $this->getFieldFromGroup($field_type);
|
||||
$this->clickLink($field_group);
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->fieldExists('field_options_wrapper')->selectOption($field_type);
|
||||
}
|
||||
$field_label = $page->findField('label');
|
||||
$this->assertTrue($field_label->isVisible());
|
||||
$field_label = $page->find('css', 'input[data-drupal-selector="edit-label"]');
|
||||
$field_label->setValue($label);
|
||||
$machine_name = $assert_session->waitForElementVisible('css', '[data-drupal-selector="edit-label"] + * .machine-name-value');
|
||||
$this->assertNotEmpty($machine_name);
|
||||
$page->findButton('Edit')->press();
|
||||
|
||||
$field_field_name = $page->findField('field_name');
|
||||
$this->assertTrue($field_field_name->isVisible());
|
||||
$field_field_name->setValue($field_name);
|
||||
|
||||
$this->assertSession()->elementExists('xpath', '//button[text()="Continue"]')->press();
|
||||
$this->assertSession()->assertWaitOnAjaxRequest();
|
||||
$this->assertSession()->waitForElementVisible('css', '#drupal-modal');
|
||||
|
||||
$assert_session->waitForText("These settings apply to the $label field everywhere it is used.");
|
||||
if ($save_settings) {
|
||||
// Second step: Save field settings.
|
||||
$save_button = $page->find('css', '.ui-dialog-buttonpane')->findButton('Save');
|
||||
$save_button->click();
|
||||
$assert_session->assert($assert_session->waitForText("Saved $label configuration."), 'text not found');
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$row = $page->find('css', '#field-' . $field_name);
|
||||
$this->assertNotEmpty($row, 'Field was created and appears in the overview page.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an existing field through the Field UI.
|
||||
*
|
||||
* @param string $bundle_path
|
||||
* Admin path of the bundle that the field is to be attached to.
|
||||
* @param string $existing_storage_name
|
||||
* The name of the existing field storage for which we want to add a new
|
||||
* field.
|
||||
* @param string|null $label
|
||||
* (optional) The label of the new field. Defaults to a random string.
|
||||
* @param array $field_edit
|
||||
* (optional) $edit parameter for submitForm() on the second step
|
||||
* ('Field settings' form).
|
||||
*/
|
||||
public function fieldUIAddExistingFieldJS(string $bundle_path, string $existing_storage_name, ?string $label = NULL, array $field_edit = []): void {
|
||||
$label = $label ?: $this->randomMachineName();
|
||||
$field_edit['edit-label'] = $label;
|
||||
|
||||
// First step: navigate to the re-use field page.
|
||||
$this->drupalGet("{$bundle_path}/fields/");
|
||||
// Confirm that the local action is visible.
|
||||
$this->assertSession()->linkExists('Re-use an existing field');
|
||||
$this->clickLink('Re-use an existing field');
|
||||
// Wait for the modal to open.
|
||||
$this->assertSession()->waitForElementVisible('css', '#drupal-modal');
|
||||
$this->assertSession()->elementExists('css', "input[value=Re-use][name=$existing_storage_name]");
|
||||
$this->click("input[value=Re-use][name=$existing_storage_name]");
|
||||
|
||||
// Set the main content to only the content region because the label can
|
||||
// contain HTML which will be auto-escaped by Twig.
|
||||
$this->assertSession()->responseContains('field-config-edit-form');
|
||||
// Check that the page does not have double escaped HTML tags.
|
||||
$this->assertSession()->responseNotContains('&lt;');
|
||||
|
||||
// Second step: 'Field settings' form.
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
$this->assertSession()->assert($this->assertSession()->waitForText("Saved $label configuration."), 'text not found');
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$xpath = $this->assertSession()->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
|
||||
':label' => $label,
|
||||
]);
|
||||
$this->assertSession()->elementExists('xpath', $xpath);
|
||||
}
|
||||
|
||||
}
|
||||
253
web/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
Normal file
253
web/core/modules/field_ui/tests/src/Traits/FieldUiTestTrait.php
Normal file
@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Traits;
|
||||
|
||||
use Behat\Mink\Exception\ElementNotFoundException;
|
||||
|
||||
/**
|
||||
* Provides common functionality for the Field UI test classes.
|
||||
*/
|
||||
trait FieldUiTestTrait {
|
||||
|
||||
/**
|
||||
* Creates a new field through the Field UI.
|
||||
*
|
||||
* @param string $bundle_path
|
||||
* Admin path of the bundle that the new field is to be attached to.
|
||||
* @param string $field_name
|
||||
* The field name of the new field storage.
|
||||
* @param string $label
|
||||
* (optional) The label of the new field. Defaults to a random string.
|
||||
* @param string $field_type
|
||||
* (optional) The field type of the new field storage. Defaults to
|
||||
* 'test_field'.
|
||||
* @param array $storage_edit
|
||||
* (optional) $edit parameter for submitForm() on the second step
|
||||
* ('Storage settings' form).
|
||||
* @param array $field_edit
|
||||
* (optional) $edit parameter for submitForm() on the third step ('Field
|
||||
* settings' form).
|
||||
* @param bool $save_settings
|
||||
* (optional) Parameter for conditional execution of second and third step
|
||||
* (Saving the storage settings and field settings). Defaults to 'TRUE'.
|
||||
*/
|
||||
public function fieldUIAddNewField($bundle_path, $field_name, $label = NULL, $field_type = 'test_field', array $storage_edit = [], array $field_edit = [], bool $save_settings = TRUE) {
|
||||
// Generate a label containing only letters and numbers to prevent random
|
||||
// test failure.
|
||||
// See https://www.drupal.org/project/drupal/issues/3030902
|
||||
$label = $label ?: $this->randomMachineName();
|
||||
$initial_edit = [
|
||||
'label' => $label,
|
||||
'field_name' => $field_name,
|
||||
];
|
||||
|
||||
// Allow the caller to set a NULL path in case they navigated to the right
|
||||
// page before calling this method.
|
||||
if ($bundle_path !== NULL) {
|
||||
$bundle_path = "$bundle_path/fields/add-field";
|
||||
// First step: 'Add field' page.
|
||||
$this->drupalGet($bundle_path);
|
||||
}
|
||||
|
||||
try {
|
||||
// First check if the passed in field type is not part of a group.
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */
|
||||
$field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$field_definitions = $field_type_plugin_manager->getUiDefinitions();
|
||||
$field_type_label = (string) $field_definitions[$field_type]['label'];
|
||||
$link = $this->assertSession()->elementExists('xpath', "//a[.//span[text()='$field_type_label']]");
|
||||
$link->click();
|
||||
|
||||
if ($this->getSession()->getPage()->hasField('field_options_wrapper')) {
|
||||
$initial_edit['field_options_wrapper'] = $field_type;
|
||||
}
|
||||
}
|
||||
// If the element could not be found then it is probably in a group.
|
||||
catch (ElementNotFoundException) {
|
||||
// Call the helper function to confirm it is in a group.
|
||||
$field_group = $this->getFieldFromGroup($field_type);
|
||||
// Pass in the group name as the new storage type.
|
||||
$this->clickLink($field_group);
|
||||
$initial_edit['field_options_wrapper'] = $field_type;
|
||||
}
|
||||
$this->submitForm($initial_edit, 'Continue');
|
||||
if ($save_settings) {
|
||||
$this->assertSession()->pageTextContains("These settings apply to the $label field everywhere it is used.");
|
||||
|
||||
// Ensure that each array key in $storage_edit is prefixed with
|
||||
// field_storage.
|
||||
$prefixed_storage_edit = [];
|
||||
foreach ($storage_edit as $key => $value) {
|
||||
if (str_starts_with($key, 'field_storage')) {
|
||||
$prefixed_storage_edit[$key] = $value;
|
||||
continue;
|
||||
}
|
||||
// If the key starts with settings, it needs to be prefixed differently.
|
||||
if (str_starts_with($key, 'settings[')) {
|
||||
$prefixed_storage_edit[str_replace('settings[', 'field_storage[subform][settings][', $key)] = $value;
|
||||
continue;
|
||||
}
|
||||
$prefixed_storage_edit['field_storage[subform][' . $key . ']'] = $value;
|
||||
}
|
||||
|
||||
// Second step: 'Storage settings' form.
|
||||
$this->submitForm($prefixed_storage_edit, 'Update settings');
|
||||
|
||||
// Third step: 'Field settings' form.
|
||||
$this->submitForm($field_edit, 'Save');
|
||||
$this->assertSession()->pageTextContains("Saved $label configuration.");
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$this->assertFieldExistsOnOverview($label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an existing field through the Field UI.
|
||||
*
|
||||
* @param string $bundle_path
|
||||
* Admin path of the bundle that the field is to be attached to.
|
||||
* @param string $existing_storage_name
|
||||
* The name of the existing field storage for which we want to add a new
|
||||
* field.
|
||||
* @param string $label
|
||||
* (optional) The label of the new field. Defaults to a random string.
|
||||
* @param array $field_edit
|
||||
* (optional) $edit parameter for submitForm() on the second step
|
||||
* ('Field settings' form).
|
||||
*/
|
||||
public function fieldUIAddExistingField($bundle_path, $existing_storage_name, $label = NULL, array $field_edit = []) {
|
||||
$label = $label ?: $this->randomMachineName();
|
||||
$field_edit['edit-label'] = $label;
|
||||
|
||||
// First step: navigate to the re-use field page.
|
||||
$this->drupalGet("{$bundle_path}/fields/");
|
||||
// Confirm that the local action is visible.
|
||||
$this->assertSession()->linkExists('Re-use an existing field');
|
||||
$this->clickLink('Re-use an existing field');
|
||||
$this->assertSession()->elementExists('css', "input[value=Re-use][name=$existing_storage_name]");
|
||||
$this->click("input[value=Re-use][name=$existing_storage_name]");
|
||||
|
||||
// Set the main content to only the content region because the label can
|
||||
// contain HTML which will be auto-escaped by Twig.
|
||||
$this->assertSession()->responseContains('field-config-edit-form');
|
||||
// Check that the page does not have double escaped HTML tags.
|
||||
$this->assertSession()->responseNotContains('&lt;');
|
||||
|
||||
// Second step: 'Field settings' form.
|
||||
$this->submitForm($field_edit, 'Save settings');
|
||||
$this->assertSession()->pageTextContains("Saved $label configuration.");
|
||||
|
||||
// Check that the field appears in the overview form.
|
||||
$xpath = $this->assertSession()->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
|
||||
':label' => $label,
|
||||
]);
|
||||
$this->assertSession()->elementExists('xpath', $xpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a field through the Field UI.
|
||||
*
|
||||
* @param string $bundle_path
|
||||
* Admin path of the bundle that the field is to be deleted from.
|
||||
* @param string $field_name
|
||||
* The name of the field.
|
||||
* @param string $label
|
||||
* The label of the field.
|
||||
* @param string $bundle_label
|
||||
* The label of the bundle.
|
||||
* @param string $source_label
|
||||
* (optional) The label of the source entity type bundle.
|
||||
*/
|
||||
public function fieldUIDeleteField($bundle_path, $field_name, $label, $bundle_label, string $source_label = '') {
|
||||
// Display confirmation form.
|
||||
$this->drupalGet("$bundle_path/fields/$field_name/delete");
|
||||
$this->assertSession()->pageTextContains("Are you sure you want to delete the field $label");
|
||||
|
||||
// Submit confirmation form.
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains("The field $label has been deleted from the $bundle_label $source_label");
|
||||
|
||||
// Check that the field does not appear in the overview form.
|
||||
$xpath = $this->assertSession()->buildXPathQuery('//table[@id="field-overview"]//span[@class="label-field" and text()= :label]', [
|
||||
':label' => $label,
|
||||
]);
|
||||
$this->assertSession()->elementNotExists('xpath', $xpath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that returns the name of the group that a field is in.
|
||||
*
|
||||
* @param string $field_type
|
||||
* The name of the field type.
|
||||
*
|
||||
* @return string|null
|
||||
* Group name
|
||||
*/
|
||||
public function getFieldFromGroup($field_type) {
|
||||
/** @var \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_plugin_manager */
|
||||
$field_type_plugin_manager = \Drupal::service('plugin.manager.field.field_type');
|
||||
$grouped_field_types = $field_type_plugin_manager->getGroupedDefinitions($field_type_plugin_manager->getUiDefinitions());
|
||||
foreach ($grouped_field_types as $group => $field_types) {
|
||||
if (array_key_exists($field_type, $field_types)) {
|
||||
return $group;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field appears on the overview form.
|
||||
*
|
||||
* @param string $label
|
||||
* The field label.
|
||||
*
|
||||
* @throws \Behat\Mink\Exception\ElementNotFoundException
|
||||
*/
|
||||
protected function assertFieldExistsOnOverview(string $label) {
|
||||
$xpath = $this->assertSession()
|
||||
->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
|
||||
':label' => $label,
|
||||
]);
|
||||
$element = $this->getSession()->getPage()->find('xpath', $xpath);
|
||||
if ($element === NULL) {
|
||||
throw new ElementNotFoundException($this->getSession()->getDriver(), 'form field', 'label', $label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the field does not appear on the overview form.
|
||||
*
|
||||
* @param string $label
|
||||
* The field label.
|
||||
*/
|
||||
protected function assertFieldDoesNotExistOnOverview(string $label) {
|
||||
$xpath = $this->assertSession()
|
||||
->buildXPathQuery("//table[@id=\"field-overview\"]//tr/td[1 and text() = :label]", [
|
||||
':label' => $label,
|
||||
]);
|
||||
$element = $this->getSession()->getPage()->find('xpath', $xpath);
|
||||
$this->assertSession()->assert($element === NULL, sprintf('A field "%s" appears on this page, but it should not.', $label));
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a header cell appears on a table.
|
||||
*
|
||||
* @param string $table_id
|
||||
* The HTML attribute value to target a given table.
|
||||
* @param string $label
|
||||
* The cell label.
|
||||
*/
|
||||
protected function assertTableHeaderExistsByLabel(string $table_id, string $label): void {
|
||||
$expression = '//table[@id=:id]//tr//th[1 and text() = :label]';
|
||||
$xpath = $this->assertSession()->buildXPathQuery($expression, [
|
||||
':id' => $table_id,
|
||||
':label' => $label,
|
||||
]);
|
||||
$element = $this->getSession()->getPage()->find('xpath', $xpath);
|
||||
$this->assertSession()->assert($element !== NULL, sprintf('Table header not found by label: "%s".', $label));
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Unit;
|
||||
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\Core\Render\ElementInfoManagerInterface;
|
||||
use Drupal\Core\TempStore\PrivateTempStore;
|
||||
use Drupal\field_ui\Form\FieldConfigEditForm;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\field_ui\Form\FieldConfigEditForm
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldConfigEditFormTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The field config edit form.
|
||||
*
|
||||
* @var \Drupal\field_ui\Form\FieldConfigEditForm|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $fieldConfigEditForm;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$entity_type_bundle_info = $this->createMock('\Drupal\Core\Entity\EntityTypeBundleInfoInterface');
|
||||
$typed_data = $this->createMock('\Drupal\Core\TypedData\TypedDataManagerInterface');
|
||||
$temp_store = $this->createMock(PrivateTempStore::class);
|
||||
$element_info_manager = $this->createMock(ElementInfoManagerInterface::class);
|
||||
$entity_display_repository = $this->createMock(EntityDisplayRepositoryInterface::class);
|
||||
$this->fieldConfigEditForm = new FieldConfigEditForm($entity_type_bundle_info, $typed_data, $entity_display_repository, $temp_store, $element_info_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::hasAnyRequired
|
||||
*
|
||||
* @dataProvider providerRequired
|
||||
*/
|
||||
public function testHasAnyRequired(array $element, bool $result): void {
|
||||
$reflection = new \ReflectionClass('\Drupal\field_ui\Form\FieldConfigEditForm');
|
||||
$method = $reflection->getMethod('hasAnyRequired');
|
||||
$this->assertEquals($result, $method->invoke($this->fieldConfigEditForm, $element));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test cases with required and optional elements.
|
||||
*/
|
||||
public static function providerRequired(): \Generator {
|
||||
yield 'required' => [
|
||||
[['#required' => TRUE]],
|
||||
TRUE,
|
||||
];
|
||||
yield 'optional' => [
|
||||
[['#required' => FALSE]],
|
||||
FALSE,
|
||||
];
|
||||
yield 'required and optional' => [
|
||||
[['#required' => TRUE], ['#required' => FALSE]],
|
||||
TRUE,
|
||||
];
|
||||
yield 'empty' => [
|
||||
[[], []],
|
||||
FALSE,
|
||||
];
|
||||
yield 'multiple required' => [
|
||||
[[['#required' => TRUE]], [['#required' => TRUE]]],
|
||||
TRUE,
|
||||
];
|
||||
yield 'multiple optional' => [
|
||||
[[['#required' => FALSE]], [['#required' => FALSE]]],
|
||||
FALSE,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Unit;
|
||||
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\field_ui\Element\FieldUiTable
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldUiTableTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @covers ::reduceOrder
|
||||
*
|
||||
* @dataProvider providerTestReduceOrder
|
||||
*/
|
||||
public function testReduceOrder($array, $expected): void {
|
||||
$this->assertSame($expected, array_reduce($array, ['Drupal\field_ui\Element\FieldUiTable', 'reduceOrder']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for testReduceOrder().
|
||||
*/
|
||||
public static function providerTestReduceOrder() {
|
||||
return [
|
||||
'Flat' => [
|
||||
'array' => [
|
||||
[
|
||||
'name' => 'foo',
|
||||
],
|
||||
[
|
||||
'name' => 'bar',
|
||||
],
|
||||
[
|
||||
'name' => 'baz',
|
||||
],
|
||||
],
|
||||
'expected' => ['foo', 'bar', 'baz'],
|
||||
],
|
||||
'Nested' => [
|
||||
'array' => [
|
||||
[
|
||||
'name' => 'foo',
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'bar',
|
||||
'weight' => 0,
|
||||
],
|
||||
[
|
||||
'name' => 'baz',
|
||||
'weight' => -1,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'biz',
|
||||
],
|
||||
],
|
||||
'expected' => ['foo', 'baz', 'bar', 'biz'],
|
||||
],
|
||||
'Nested no name key' => [
|
||||
'array' => [
|
||||
[
|
||||
'children' => [
|
||||
[
|
||||
'name' => 'foo',
|
||||
'weight' => -1,
|
||||
],
|
||||
[
|
||||
'name' => 'bar',
|
||||
'weight' => 1,
|
||||
],
|
||||
[
|
||||
'name' => 'baz',
|
||||
'weight' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'biz',
|
||||
],
|
||||
],
|
||||
'expected' => ['foo', 'baz', 'bar', 'biz'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
74
web/core/modules/field_ui/tests/src/Unit/FieldUiTest.php
Normal file
74
web/core/modules/field_ui/tests/src/Unit/FieldUiTest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\field_ui\Unit;
|
||||
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\field_ui\FieldUI;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\field_ui\FieldUI
|
||||
*
|
||||
* @group field_ui
|
||||
*/
|
||||
class FieldUiTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The path validator.
|
||||
*
|
||||
* @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
protected $pathValidator;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->pathValidator = $this->createMock('Drupal\Core\Path\PathValidatorInterface');
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('path.validator', $this->pathValidator);
|
||||
\Drupal::setContainer($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getNextDestination
|
||||
*/
|
||||
public function testGetNextDestination(): void {
|
||||
$destinations = ['admin', 'admin/content'];
|
||||
$expected_uri = 'base:admin';
|
||||
$expected_query = [
|
||||
'destinations' => ['admin/content'],
|
||||
];
|
||||
$actual = FieldUI::getNextDestination($destinations);
|
||||
$this->assertSame($expected_uri, $actual->getUri());
|
||||
$this->assertSame($expected_query, $actual->getOption('query'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getNextDestination
|
||||
*/
|
||||
public function testGetNextDestinationEmpty(): void {
|
||||
$destinations = [];
|
||||
$actual = FieldUI::getNextDestination($destinations);
|
||||
$this->assertNull($actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers ::getNextDestination
|
||||
*/
|
||||
public function testGetNextDestinationRouteName(): void {
|
||||
$destinations = [['route_name' => 'system.admin'], ['route_name' => 'system.admin_content']];
|
||||
$expected_route_name = 'system.admin';
|
||||
$expected_query = [
|
||||
'destinations' => [['route_name' => 'system.admin_content']],
|
||||
];
|
||||
$actual = FieldUI::getNextDestination($destinations);
|
||||
$this->assertSame($expected_route_name, $actual->getRouteName());
|
||||
$this->assertSame($expected_query, $actual->getOption('query'));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user