From b7ee3dcfcedad41a1c1c875811dd381f7f7fb059 Mon Sep 17 00:00:00 2001 From: Lauri Eskola <lauri.eskola@acquia.com> Date: Tue, 1 Aug 2023 18:34:53 +0300 Subject: [PATCH] Issue #3377030 by Wim Leers, borisson_, smustgrave: Add validation constraint to `type: label`: disallow multiple lines --- core/config/schema/core.data_types.schema.yml | 9 ++++ .../tests/src/Kernel/BlockValidationTest.php | 27 ++++++++++ .../contact/config/schema/contact.schema.yml | 2 +- .../src/Kernel/ContactFormValidationTest.php | 11 ++++ .../tests/src/Kernel/EditorValidationTest.php | 9 ++++ .../Entity/FieldConfigValidationTest.php | 33 ++++++++++++ .../FieldStorageConfigValidationTest.php | 1 + .../ContentLanguageSettingsValidationTest.php | 5 ++ ...BuilderEntityViewDisplayValidationTest.php | 9 ++++ .../RestResourceConfigValidationTest.php | 5 ++ .../text/config/schema/text.schema.yml | 2 +- .../Config/ConfigEntityValidationTestBase.php | 52 +++++++++++++++++++ .../EntityFormDisplayValidationTest.php | 50 +++++++++++++++++- .../EntityViewDisplayValidationTest.php | 6 ++- 14 files changed, 217 insertions(+), 4 deletions(-) diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 763853c1b460..f43dbb50543b 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -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: diff --git a/core/modules/block/tests/src/Kernel/BlockValidationTest.php b/core/modules/block/tests/src/Kernel/BlockValidationTest.php index c055f29203d3..f9acd355bdd9 100644 --- a/core/modules/block/tests/src/Kernel/BlockValidationTest.php +++ b/core/modules/block/tests/src/Kernel/BlockValidationTest.php @@ -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 @@ protected function setUp(): void { 'id' => 'test_block', 'theme' => 'stark', 'plugin' => 'system_powered_by_block', + 'settings' => [ + 'label' => 'Powered by Drupal 🚀', + ], ]); $this->entity->save(); } @@ -62,4 +66,27 @@ public function providerInvalidMachineNameCharacters(): array { 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."]); + } + } diff --git a/core/modules/contact/config/schema/contact.schema.yml b/core/modules/contact/config/schema/contact.schema.yml index 5fb69d63393a..7e9fd9fee4ed 100644 --- a/core/modules/contact/config/schema/contact.schema.yml +++ b/core/modules/contact/config/schema/contact.schema.yml @@ -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 diff --git a/core/modules/contact/tests/src/Kernel/ContactFormValidationTest.php b/core/modules/contact/tests/src/Kernel/ContactFormValidationTest.php index b84b2ff626be..f07571c142d0 100644 --- a/core/modules/contact/tests/src/Kernel/ContactFormValidationTest.php +++ b/core/modules/contact/tests/src/Kernel/ContactFormValidationTest.php @@ -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 @@ protected function setUp(): void { $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([]); + } + } diff --git a/core/modules/editor/tests/src/Kernel/EditorValidationTest.php b/core/modules/editor/tests/src/Kernel/EditorValidationTest.php index 670b7225c421..5c6b1b87e3a8 100644 --- a/core/modules/editor/tests/src/Kernel/EditorValidationTest.php +++ b/core/modules/editor/tests/src/Kernel/EditorValidationTest.php @@ -70,4 +70,13 @@ public function testInvalidPluginId(): void { $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(); + } + } diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php index fde68011cea0..c4c4709e1191 100644 --- a/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php +++ b/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php @@ -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 @@ public function testInvalidDependencies(): void { ]); } + /** + * 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([]); + } + } diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php index 95ba5ec42bd8..3ffbd0292048 100644 --- a/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php +++ b/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php @@ -22,6 +22,7 @@ class FieldStorageConfigValidationTest extends ConfigEntityValidationTestBase { */ protected function setUp(): void { parent::setUp(); + $this->installEntitySchema('user'); $this->entity = FieldStorageConfig::create([ 'type' => 'boolean', diff --git a/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php b/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php index 01a2e6a77ed9..07bab877aafb 100644 --- a/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php +++ b/core/modules/language/tests/src/Kernel/ContentLanguageSettingsValidationTest.php @@ -17,6 +17,11 @@ class ContentLanguageSettingsValidationTest extends ConfigEntityValidationTestBa */ protected static $modules = ['language', 'user']; + /** + * {@inheritdoc} + */ + protected bool $hasLabel = FALSE; + /** * {@inheritdoc} */ diff --git a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayValidationTest.php b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayValidationTest.php index 0c6032afb6a5..740b531cc47f 100644 --- a/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayValidationTest.php +++ b/core/modules/layout_builder/tests/src/Kernel/LayoutBuilderEntityViewDisplayValidationTest.php @@ -39,4 +39,13 @@ protected function setUp(): void { $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(); + } + } diff --git a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php index 0d8cb96272cb..1859afa5dd56 100644 --- a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php +++ b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php @@ -18,6 +18,11 @@ class RestResourceConfigValidationTest extends ConfigEntityValidationTestBase { */ protected static $modules = ['rest', 'serialization']; + /** + * {@inheritdoc} + */ + protected bool $hasLabel = FALSE; + /** * {@inheritdoc} */ diff --git a/core/modules/text/config/schema/text.schema.yml b/core/modules/text/config/schema/text.schema.yml index e0fa5d3d83da..bd9cdb09cc0f 100644 --- a/core/modules/text/config/schema/text.schema.yml +++ b/core/modules/text/config/schema/text.schema.yml @@ -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 diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php index c0cf4928c68f..aeed2a6737ce 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigEntityValidationTestBase.php @@ -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 @@ public function testConfigDependenciesValidation(array $dependencies, array $exp $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. * diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php index cda176050827..2b2eecdb2dcb 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php @@ -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 @@ */ class EntityFormDisplayValidationTest extends EntityFormModeValidationTest { + /** + * {@inheritdoc} + */ + protected bool $hasLabel = FALSE; + /** * {@inheritdoc} */ @@ -19,7 +27,6 @@ protected function setUp(): void { parent::setUp(); $this->entity = EntityFormDisplay::create([ - 'label' => 'Test', 'targetEntityType' => 'user', 'bundle' => 'user', // The mode was created by the parent class. @@ -28,4 +35,45 @@ protected function setUp(): void { $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([]); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php index b30998db1ac0..8b93ce26babd 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php @@ -12,6 +12,11 @@ */ class EntityViewDisplayValidationTest extends EntityViewModeValidationTest { + /** + * {@inheritdoc} + */ + protected bool $hasLabel = FALSE; + /** * {@inheritdoc} */ @@ -19,7 +24,6 @@ protected function setUp(): void { parent::setUp(); $this->entity = EntityViewDisplay::create([ - 'label' => 'Test', 'targetEntityType' => 'user', 'bundle' => 'user', // The mode was created by the parent class. -- GitLab