diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 763853c1b46..f43dbb50543 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 c055f29203d..f9acd355bdd 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 @@ 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."]); + } + } diff --git a/core/modules/contact/config/schema/contact.schema.yml b/core/modules/contact/config/schema/contact.schema.yml index 5fb69d63393..7e9fd9fee4e 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 b84b2ff626b..f07571c142d 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 @@ 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([]); + } + } diff --git a/core/modules/editor/tests/src/Kernel/EditorValidationTest.php b/core/modules/editor/tests/src/Kernel/EditorValidationTest.php index 670b7225c42..5c6b1b87e3a 100644 --- a/core/modules/editor/tests/src/Kernel/EditorValidationTest.php +++ b/core/modules/editor/tests/src/Kernel/EditorValidationTest.php @@ -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(); + } + } diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldConfigValidationTest.php index fde68011cea..c4c4709e119 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 @@ 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([]); + } + } diff --git a/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php b/core/modules/field/tests/src/Kernel/Entity/FieldStorageConfigValidationTest.php index 95ba5ec42bd..3ffbd029204 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 01a2e6a77ed..07bab877aaf 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 0c6032afb6a..740b531cc47 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 @@ 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(); + } + } diff --git a/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php b/core/modules/rest/tests/src/Kernel/Entity/RestResourceConfigValidationTest.php index 0d8cb96272c..1859afa5dd5 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 e0fa5d3d83d..bd9cdb09cc0 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 c0cf4928c68..aeed2a6737c 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 @@ 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. * diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityFormDisplayValidationTest.php index cda17605082..2b2eecdb2dc 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 @@ 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([]); + } + } diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php index b30998db1ac..8b93ce26bab 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityViewDisplayValidationTest.php @@ -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.