Verified Commit 6723b54a authored by Lee Rowlands's avatar Lee Rowlands
Browse files

task: #3612411 Split a couple of methods out of ManageFieldsFunctionalTest and...

task: #3612411 Split a couple of methods out of ManageFieldsFunctionalTest and convert them to kernel tests

By: catch
By: smustgrave
By: mstrelan
(cherry picked from commit b65177d2)
parent 0c28f882
Loading
Loading
Loading
Loading
Loading
+1 −101
Original line number Diff line number Diff line
@@ -4,7 +4,6 @@

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;
@@ -15,6 +14,7 @@
 * Tests the Field UI "Manage fields" screen.
 */
#[Group('field_ui')]
#[Group('#slow')]
#[RunTestsInSeparateProcesses]
class ManageFieldsFunctionalTest extends ManageFieldsFunctionalTestBase {

@@ -110,106 +110,6 @@ public function testDefaultValue(): void {
    $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[3]', [':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.
   */
+205 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\field_ui\Kernel;

use Behat\Mink\Exception\ElementNotFoundException;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase;
use Drupal\taxonomy\Entity\Vocabulary;
use Drupal\Tests\field\Traits\EntityReferenceFieldCreationTrait;
use Drupal\Tests\field_ui\Traits\FieldUiAssertTrait;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;

/**
 * Tests the Field UI "Manage fields" screen.
 */
#[Group('field_ui')]
#[RunTestsInSeparateProcesses]
class ManageFieldsTest extends KernelTestBase {

  use ContentTypeCreationTrait {
    createContentType as drupalCreateContentType;
  }
  use EntityReferenceFieldCreationTrait;
  use FieldUiAssertTrait;
  use UserCreationTrait {
    createUser as drupalCreateUser;
  }

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'entity_test',
    'field',
    'field_test',
    'field_ui',
    'filter',
    'node',
    'system',
    'taxonomy',
    'text',
    'user',
  ];

  /**
   * The ID of the custom content type created for testing.
   *
   * @var string
   */
  protected string $contentType;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->installEntitySchema('node');
    $this->installEntitySchema('user');
    $this->installConfig(['field_ui', 'user']);

    // Create a test user.
    $admin_user = $this->drupalCreateUser([
      'administer node fields',
    ]);
    $this->setCurrentUser($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 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();

    $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();
  }

  /**
   * 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->getNodeElementsByXpath('//tr[@id=:field_name]/td[3]', [':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']");
      }
    }
  }

}
+82 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Tests\field_ui\Traits;

/**
 * Provides assertions and helpers for Field UI test classes.
 */
trait FieldUiAssertTrait {

  /**
   * 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
   */
  protected function getFieldFromGroup($field_type): ?string {
    /** @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): void {
    $field_labels = array_map(
      fn ($element): string => html_entity_decode($element->getHtml()),
      $this->getSession()->getPage()->findAll('css', 'table#field-overview tr td:first-child .field-label-text'),
    );
    $this->assertContains($label, $field_labels);
  }

  /**
   * Asserts that the field does not appear on the overview form.
   *
   * @param string $label
   *   The field label.
   */
  protected function assertFieldDoesNotExistOnOverview(string $label): void {
    $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));
  }

}
+2 −70
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@
 */
trait FieldUiTestTrait {

  use FieldUiAssertTrait;

  /**
   * Creates a new field through the Field UI.
   *
@@ -174,74 +176,4 @@ public function fieldUIDeleteField($bundle_path, $field_name, $label, $bundle_la
    $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): ?string {
    /** @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): void {
    $field_labels = array_map(
      fn ($element): string => html_entity_decode($element->getHtml()),
      $this->getSession()->getPage()->findAll('css', 'table#field-overview tr td:first-child .field-label-text'),
    );
    $this->assertContains($label, $field_labels);
  }

  /**
   * Asserts that the field does not appear on the overview form.
   *
   * @param string $label
   *   The field label.
   */
  protected function assertFieldDoesNotExistOnOverview(string $label): void {
    $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));
  }

}