Issue #3377030 by Wim Leers, borisson_, smustgrave: Add validation constraint to `type: label`: disallow multiple lines

merge-requests/4523/head
Lauri Eskola 2023-08-01 18:34:53 +03:00
parent 4cb10842ee
commit b7ee3dcfce
No known key found for this signature in database
GPG Key ID: 382FC0F5B0DF53F8
14 changed files with 217 additions and 4 deletions

View File

@ -62,6 +62,15 @@ label:
type: string
label: 'Label'
translatable: true
constraints:
Regex:
# Forbid any kind of line ending:
# - Windows: `\r\n`
# - old macOS: `\r`
# - *nix: `\n`
pattern: '/(\r\n|\r|\n)/'
match: false
message: 'Labels are not allowed to span multiple lines.'
# String containing plural variants, separated by EXT.
plural_label:

View File

@ -3,6 +3,7 @@
namespace Drupal\Tests\block\Kernel;
use Drupal\block\Entity\Block;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
/**
@ -27,6 +28,9 @@ class BlockValidationTest extends ConfigEntityValidationTestBase {
'id' => 'test_block',
'theme' => 'stark',
'plugin' => 'system_powered_by_block',
'settings' => [
'label' => 'Powered by Drupal 🚀',
],
]);
$this->entity->save();
}
@ -62,4 +66,27 @@ class BlockValidationTest extends ConfigEntityValidationTestBase {
return $cases;
}
/**
* {@inheritdoc}
*/
protected static function setLabel(ConfigEntityInterface $block, string $label): void {
static::assertInstanceOf(Block::class, $block);
$settings = $block->get('settings');
static::assertNotEmpty($settings['label']);
$settings['label'] = $label;
$block->set('settings', $settings);
}
/**
* {@inheritdoc}
*/
public function testLabelValidation(): void {
static::setLabel($this->entity, "Multi\nLine");
// TRICKY: because the Block config entity type does not specify a `label`
// key, it is impossible for the generic ::testLabelValidation()
// implementation in the base class to know at which property to expect a
// validation error. Hence it is hardcoded in this case.
$this->assertValidationErrors(['settings.label' => "Labels are not allowed to span multiple lines."]);
}
}

View File

@ -28,7 +28,7 @@ contact.form.*:
type: integer
label: 'Weight'
message:
type: label
type: text
label: 'Message displayed to user on submission'
redirect:
type: path

View File

@ -2,6 +2,7 @@
namespace Drupal\Tests\contact\Kernel;
use Drupal\contact\ContactFormInterface;
use Drupal\contact\Entity\ContactForm;
use Drupal\KernelTests\Core\Config\ConfigEntityValidationTestBase;
@ -30,4 +31,14 @@ class ContactFormValidationTest extends ConfigEntityValidationTestBase {
$this->entity->save();
}
/**
* Tests validation of message.
*/
public function testMessageValidation(): void {
assert($this->entity instanceof ContactFormInterface);
// Messages should be able to span multiple lines.
$this->entity->setMessage("Multi\nLine");
$this->assertValidationErrors([]);
}
}

View File

@ -70,4 +70,13 @@ class EditorValidationTest extends ConfigEntityValidationTestBase {
$this->assertValidationErrors(['editor' => "The 'non_existent' plugin does not exist."]);
}
/**
* {@inheritdoc}
*/
public function testLabelValidation(): void {
// @todo Remove this override in https://www.drupal.org/i/3231354. The label of Editor entities is dynamically computed: it's retrieved from the associated FilterFormat entity. That issue will change this.
// @see \Drupal\editor\Entity\Editor::label()
$this->markTestSkipped();
}
}

View File

@ -3,6 +3,7 @@
namespace Drupal\Tests\field\Kernel\Entity;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests validation of field_config entities.
@ -53,4 +54,36 @@ class FieldConfigValidationTest extends FieldStorageConfigValidationTest {
]);
}
/**
* Tests validation of a field_config's default value.
*/
public function testMultilineTextFieldDefaultValue(): void {
// First, create a field storage for which a complex default value exists.
$this->enableModules(['text']);
$text_field_storage_config = FieldStorageConfig::create([
'type' => 'text_with_summary',
'field_name' => 'novel',
'entity_type' => 'user',
]);
$text_field_storage_config->save();
$this->entity = FieldConfig::create([
'field_storage' => $text_field_storage_config,
'bundle' => 'user',
'default_value' => [
0 => [
'value' => "Multi\nLine",
'summary' => '',
'format' => 'basic_html',
],
],
'dependencies' => [
'config' => [
$text_field_storage_config->getConfigDependencyName(),
],
],
]);
$this->assertValidationErrors([]);
}
}

View File

@ -22,6 +22,7 @@ class FieldStorageConfigValidationTest extends ConfigEntityValidationTestBase {
*/
protected function setUp(): void {
parent::setUp();
$this->installEntitySchema('user');
$this->entity = FieldStorageConfig::create([
'type' => 'boolean',

View File

@ -17,6 +17,11 @@ class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBa
*/
protected static $modules = ['language', 'user'];
/**
* {@inheritdoc}
*/
protected bool $hasLabel = FALSE;
/**
* {@inheritdoc}
*/

View File

@ -39,4 +39,13 @@ class LayoutBuilderEntityViewDisplayValidationTest extends ConfigEntityValidatio
$this->entity->save();
}
/**
* {@inheritdoc}
*/
public function testLabelValidation(): void {
// @todo Remove this override in https://www.drupal.org/i/2939931. The label of Layout Builder's EntityViewDisplay override is computed dynamically, that issue will change this.
// @see \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::label()
$this->markTestSkipped();
}
}

View File

@ -18,6 +18,11 @@ class RestResourceConfigValidationTest extends ConfigEntityValidationTestBase {
*/
protected static $modules = ['rest', 'serialization'];
/**
* {@inheritdoc}
*/
protected bool $hasLabel = FALSE;
/**
* {@inheritdoc}
*/

View File

@ -138,7 +138,7 @@ field.widget.settings.text_textarea_with_summary:
type: integer
label: 'Number of summary rows'
placeholder:
type: label
type: text
label: 'Placeholder'
show_summary:
type: boolean

View File

@ -30,6 +30,20 @@ abstract class ConfigEntityValidationTestBase extends KernelTestBase {
*/
protected ConfigEntityInterface $entity;
/**
* Whether a config entity of this type has a label.
*
* Most config entity types ensure their entities have a label. But a few do
* not, typically highly abstract/very low level config entities without a
* strong UI presence. For example: REST resource configuration entities and
* entity view displays.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
*
* @var bool
*/
protected bool $hasLabel = TRUE;
/**
* {@inheritdoc}
*/
@ -288,6 +302,44 @@ abstract class ConfigEntityValidationTestBase extends KernelTestBase {
$this->assertValidationErrors($expected_enforced_messages);
}
/**
* Tests validation of config entity's label.
*
* @see \Drupal\Core\Entity\EntityInterface::label()
* @see \Drupal\Core\Entity\EntityBase::label()
*/
public function testLabelValidation(): void {
// Some entity types do not have a label.
if (!$this->hasLabel) {
$this->markTestSkipped();
}
if ($this->entity->getEntityType()->getKey('label') === $this->entity->getEntityType()->getKey('id')) {
$this->markTestSkipped('This entity type uses the ID as the label; an entity without a label is hence impossible.');
}
static::setLabel($this->entity, "Multi\nLine");
$this->assertValidationErrors([$this->entity->getEntityType()->getKey('label') => "Labels are not allowed to span multiple lines."]);
}
/**
* Sets the label of the given config entity.
*
* @param \Drupal\Core\Config\Entity\ConfigEntityInterface $entity
* The config entity to modify.
* @param string $label
* The label to set.
*
* @see ::testLabelValidation()
*/
protected static function setLabel(ConfigEntityInterface $entity, string $label): void {
$label_property = $entity->getEntityType()->getKey('label');
if ($label_property === FALSE) {
throw new \LogicException(sprintf('Override %s to allow testing a %s without a label.', __METHOD__, (string) $entity->getEntityType()->getSingularLabel()));
}
$entity->set($label_property, $label);
}
/**
* Asserts a set of validation errors is raised when the entity is validated.
*

View File

@ -2,7 +2,10 @@
namespace Drupal\KernelTests\Core\Entity;
use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests validation of entity_form_display entities.
@ -12,6 +15,11 @@ use Drupal\Core\Entity\Entity\EntityFormDisplay;
*/
class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
/**
* {@inheritdoc}
*/
protected bool $hasLabel = FALSE;
/**
* {@inheritdoc}
*/
@ -19,7 +27,6 @@ class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
parent::setUp();
$this->entity = EntityFormDisplay::create([
'label' => 'Test',
'targetEntityType' => 'user',
'bundle' => 'user',
// The mode was created by the parent class.
@ -28,4 +35,45 @@ class EntityFormDisplayValidationTest extends EntityFormModeValidationTest {
$this->entity->save();
}
/**
* Tests validation of entity form display component's widget settings.
*/
public function testMultilineTextFieldWidgetPlaceholder(): void {
// First, create a field for which widget settings exist.
$this->enableModules(['field', 'text']);
$text_field_storage_config = FieldStorageConfig::create([
'type' => 'text_with_summary',
'field_name' => 'novel',
'entity_type' => 'user',
]);
$text_field_storage_config->save();
$text_field_config = FieldConfig::create([
'field_storage' => $text_field_storage_config,
'bundle' => 'user',
'dependencies' => [
'config' => [
$text_field_storage_config->getConfigDependencyName(),
],
],
]);
$text_field_config->save();
// Then, configure a form display widget for this field.
assert($this->entity instanceof EntityFormDisplayInterface);
$this->entity->setComponent('novel', [
'type' => 'text_textarea_with_summary',
'region' => 'content',
'settings' => [
'rows' => 9,
'summary_rows' => 3,
'placeholder' => "Multi\nLine",
'show_summary' => FALSE,
],
'third_party_settings' => [],
]);
$this->assertValidationErrors([]);
}
}

View File

@ -12,6 +12,11 @@ use Drupal\Core\Entity\Entity\EntityViewDisplay;
*/
class EntityViewDisplayValidationTest extends EntityViewModeValidationTest {
/**
* {@inheritdoc}
*/
protected bool $hasLabel = FALSE;
/**
* {@inheritdoc}
*/
@ -19,7 +24,6 @@ class EntityViewDisplayValidationTest extends EntityViewModeValidationTest {
parent::setUp();
$this->entity = EntityViewDisplay::create([
'label' => 'Test',
'targetEntityType' => 'user',
'bundle' => 'user',
// The mode was created by the parent class.