From 88aecd800af2249386c68b29b55fb5fdf958f38a Mon Sep 17 00:00:00 2001 From: catch Date: Fri, 1 Mar 2024 11:18:58 +0000 Subject: [PATCH] Issue #3415041 by alexpott, Wim Leers: Creating blocks that depend on content via the config installer often generates a warning (cherry picked from commit e182bb335042f8845c0953fa0e97bd05daa1cb76) --- .../Core/Config/Entity/ConfigEntityBase.php | 4 +- .../ckeditor_test/ckeditor_test.info.yml | 6 ++ .../ckeditor_test/ckeditor_test.module | 17 ++++ .../src/Kernel/SmartDefaultSettingsTest.php | 77 +++++++-------- .../ConfigImporterMissingContentTest.php | 90 +++++++++++++++++- .../Entity/ConfigEntityBaseUnitTest.php | 94 +++++++++++++++++-- 6 files changed, 240 insertions(+), 48 deletions(-) create mode 100644 core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.info.yml create mode 100644 core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.module diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index be5840f77b4..ad50387947f 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -158,7 +158,7 @@ abstract class ConfigEntityBase extends EntityBase implements ConfigEntityInterf * {@inheritdoc} */ public function set($property_name, $value) { - if ($this instanceof EntityWithPluginCollectionInterface) { + if ($this instanceof EntityWithPluginCollectionInterface && !$this->isSyncing()) { $plugin_collections = $this->getPluginCollections(); if (isset($plugin_collections[$property_name])) { // If external code updates the settings, pass it along to the plugin. @@ -288,7 +288,7 @@ abstract class ConfigEntityBase extends EntityBase implements ConfigEntityInterf /** @var \Drupal\Core\Config\Entity\ConfigEntityStorageInterface $storage */ parent::preSave($storage); - if ($this instanceof EntityWithPluginCollectionInterface) { + if ($this instanceof EntityWithPluginCollectionInterface && !$this->isSyncing()) { // Any changes to the plugin configuration must be saved to the entity's // copy as well. foreach ($this->getPluginCollections() as $plugin_config_key => $plugin_collection) { diff --git a/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.info.yml b/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.info.yml new file mode 100644 index 00000000000..b58e70e3d88 --- /dev/null +++ b/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.info.yml @@ -0,0 +1,6 @@ +name: CKEditor Test +type: module +description: "Provides test a test editor that with the ID ckeditor." +package: Testing +dependencies: + - drupal:editor_test diff --git a/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.module b/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.module new file mode 100644 index 00000000000..6073a13a424 --- /dev/null +++ b/core/modules/ckeditor5/tests/modules/ckeditor_test/ckeditor_test.module @@ -0,0 +1,17 @@ +setSyncing(TRUE)->save(); + ])->save(); Editor::create([ 'format' => 'minimal_ckeditor_wrong_allowed_html', 'editor' => 'ckeditor', @@ -129,26 +131,25 @@ class SmartDefaultSettingsTest extends KernelTestBase { ], 'plugins' => [], ], - ])->setSyncing(TRUE)->save(); + ])->save(); FilterFormat::create( Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/filter.format.full_html.yml') - ) - ->setSyncing(TRUE) - ->save(); + )->save(); + Editor::create( Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.full_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $basic_html_format = Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/filter.format.basic_html.yml'); - FilterFormat::create($basic_html_format)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format)->save(); Editor::create( Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); FilterFormat::create( Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/filter.format.restricted_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $basic_html_format_without_image_uploads = $basic_html_format; $basic_html_format_without_image_uploads['name'] .= ' (without image uploads)'; @@ -158,7 +159,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { ['format' => 'basic_html_without_image_uploads'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setImageUploadSettings(['status' => FALSE])->setSyncing(TRUE)->save(); + )->setImageUploadSettings(['status' => FALSE])->save(); $allowed_html_parents = ['filters', 'filter_html', 'settings', 'allowed_html']; $current_value = NestedArray::getValue($basic_html_format, $allowed_html_parents); @@ -167,58 +168,58 @@ class SmartDefaultSettingsTest extends KernelTestBase { $basic_html_format_without_h4_h6['name'] .= ' (without H4 and H6)'; $basic_html_format_without_h4_h6['format'] = 'basic_html_without_h4_h6'; NestedArray::setValue($basic_html_format_without_h4_h6, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_without_h4_h6)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_without_h4_h6)->save(); Editor::create( ['format' => 'basic_html_without_h4_h6'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $new_value = str_replace(['

', '

', '

', '

', '
'], '', $current_value); $basic_html_format_without_headings = $basic_html_format; $basic_html_format_without_headings['name'] .= ' (without H*)'; $basic_html_format_without_headings['format'] = 'basic_html_without_headings'; NestedArray::setValue($basic_html_format_without_headings, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_without_headings)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_without_headings)->save(); Editor::create( ['format' => 'basic_html_without_headings'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $basic_html_format_with_pre = $basic_html_format; $basic_html_format_with_pre['name'] .= ' (with
)';
     $basic_html_format_with_pre['format'] = 'basic_html_with_pre';
     NestedArray::setValue($basic_html_format_with_pre, $allowed_html_parents, $current_value . ' 
');
-    FilterFormat::create($basic_html_format_with_pre)->setSyncing(TRUE)->save();
+    FilterFormat::create($basic_html_format_with_pre)->save();
     Editor::create(
       ['format' => 'basic_html_with_pre']
       +
       Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml')
-    )->setSyncing(TRUE)->save();
+    )->save();
 
     $basic_html_format_with_h1 = $basic_html_format;
     $basic_html_format_with_h1['name'] .= ' (with 

)'; $basic_html_format_with_h1['format'] = 'basic_html_with_h1'; NestedArray::setValue($basic_html_format_with_h1, $allowed_html_parents, $current_value . '

'); - FilterFormat::create($basic_html_format_with_h1)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_h1)->save(); Editor::create( ['format' => 'basic_html_with_h1'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $new_value = str_replace('

', '

', $current_value); $basic_html_format_with_alignable_p = $basic_html_format; $basic_html_format_with_alignable_p['name'] .= ' (with alignable paragraph support)'; $basic_html_format_with_alignable_p['format'] = 'basic_html_with_alignable_p'; NestedArray::setValue($basic_html_format_with_alignable_p, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_with_alignable_p)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_alignable_p)->save(); Editor::create( ['format' => 'basic_html_with_alignable_p'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $basic_html_format_with_media_embed = $basic_html_format; $basic_html_format_with_media_embed['name'] .= ' (with Media Embed support)'; @@ -227,7 +228,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { $basic_html_format_with_media_embed['filters']['media_embed'] = ['status' => TRUE]; $new_value = $current_value . ' '; NestedArray::setValue($basic_html_format_with_media_embed, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_with_media_embed)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_media_embed)->save(); $basic_html_editor_with_media_embed = Editor::create( ['format' => 'basic_html_with_media_embed'] + @@ -238,7 +239,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { // pre-existing toolbar item group labeled "Media". $settings['toolbar']['rows'][0][3]['items'][] = 'DrupalMediaLibrary'; $basic_html_editor_with_media_embed->setSettings($settings); - $basic_html_editor_with_media_embed->setSyncing(TRUE)->save(); + $basic_html_editor_with_media_embed->save(); $basic_html_format_with_media_embed_view_mode_invalid = $basic_html_format_with_media_embed; $basic_html_format_with_media_embed_view_mode_invalid['name'] = ' (with Media Embed support, view mode enabled but no view modes configured)'; @@ -246,7 +247,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { $current_value_media_embed = NestedArray::getValue($basic_html_format_with_media_embed, $allowed_html_parents); $new_value = str_replace('', '', $current_value_media_embed); NestedArray::setValue($basic_html_format_with_media_embed_view_mode_invalid, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_with_media_embed_view_mode_invalid)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_media_embed_view_mode_invalid)->save(); $basic_html_editor_with_media_embed_view_mode_enabled_no_view_modes_configured = Editor::create( ['format' => 'basic_html_with_media_embed_view_mode_enabled_no_view_modes_configured'] + @@ -257,24 +258,24 @@ class SmartDefaultSettingsTest extends KernelTestBase { // pre-existing toolbar item group labeled "Media". $settings['toolbar']['rows'][0][3]['items'][] = 'DrupalMediaLibrary'; $basic_html_editor_with_media_embed_view_mode_enabled_no_view_modes_configured->setSettings($settings); - $basic_html_editor_with_media_embed_view_mode_enabled_no_view_modes_configured->setSyncing(TRUE)->save(); + $basic_html_editor_with_media_embed_view_mode_enabled_no_view_modes_configured->save(); $new_value = str_replace('', '', $current_value); $basic_html_format_with_any_data_attr = $basic_html_format; $basic_html_format_with_any_data_attr['name'] .= ' (with any data-* attribute on images)'; $basic_html_format_with_any_data_attr['format'] = 'basic_html_with_any_data_attr'; NestedArray::setValue($basic_html_format_with_any_data_attr, $allowed_html_parents, $new_value); - FilterFormat::create($basic_html_format_with_any_data_attr)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_any_data_attr)->save(); Editor::create( ['format' => 'basic_html_with_any_data_attr'] + Yaml::parseFile('core/modules/ckeditor5/tests/fixtures/ckeditor4_config/editor.editor.basic_html.yml') - )->setSyncing(TRUE)->save(); + )->save(); $basic_html_format_with_media_embed_view_mode_enabled_two_view_modes_configured = $basic_html_format_with_media_embed_view_mode_invalid; $basic_html_format_with_media_embed_view_mode_enabled_two_view_modes_configured['name'] = ' (with Media Embed support, view mode enabled and two view modes configured )'; $basic_html_format_with_media_embed_view_mode_enabled_two_view_modes_configured['format'] = 'basic_html_with_media_embed_view_mode_enabled_two_view_modes_configured'; - FilterFormat::create($basic_html_format_with_media_embed_view_mode_enabled_two_view_modes_configured)->setSyncing(TRUE)->save(); + FilterFormat::create($basic_html_format_with_media_embed_view_mode_enabled_two_view_modes_configured)->save(); $basic_html_editor_with_media_embed_view_mode_enabled_two_view_modes_configured = Editor::create( ['format' => 'basic_html_with_media_embed_view_mode_enabled_two_view_modes_configured'] + @@ -285,21 +286,21 @@ class SmartDefaultSettingsTest extends KernelTestBase { // pre-existing toolbar item group labeled "Media". $settings['toolbar']['rows'][0][3]['items'][] = 'DrupalMediaLibrary'; $basic_html_editor_with_media_embed_view_mode_enabled_two_view_modes_configured->setSettings($settings); - $basic_html_editor_with_media_embed_view_mode_enabled_two_view_modes_configured->setSyncing(TRUE)->save(); + $basic_html_editor_with_media_embed_view_mode_enabled_two_view_modes_configured->save(); EntityViewMode::create([ 'id' => 'media.view_mode_1', 'targetEntityType' => 'media', 'status' => TRUE, 'enabled' => TRUE, 'label' => 'View Mode 1', - ])->setSyncing(TRUE)->save(); + ])->save(); EntityViewMode::create([ 'id' => 'media.view_mode_2', 'targetEntityType' => 'media', 'status' => TRUE, 'enabled' => TRUE, 'label' => 'View Mode 2', - ])->setSyncing(TRUE)->save(); + ])->save(); $filter_format = FilterFormat::load('basic_html_with_media_embed_view_mode_enabled_two_view_modes_configured'); $filter_format->setFilterConfig('media_embed', [ 'status' => TRUE, @@ -311,7 +312,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { 'view_mode_2' => 'view_mode_2', ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); $filter_plugin_manager = $this->container->get('plugin.manager.filter'); FilterFormat::create([ @@ -323,12 +324,12 @@ class SmartDefaultSettingsTest extends KernelTestBase { 'settings' => $filter_plugin_manager->getDefinition('filter_html')['settings'], ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); FilterFormat::create([ 'format' => 'cke4_plugins_with_settings', 'name' => 'All CKEditor 4 core plugins with settings', - ])->setSyncing(TRUE)->save(); + ])->save(); Editor::create([ 'format' => 'cke4_plugins_with_settings', 'editor' => 'ckeditor', @@ -368,7 +369,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { ], ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); FilterFormat::create([ 'format' => 'cke4_stylescombo_span', @@ -381,7 +382,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { ] + $filter_plugin_manager->getDefinition('filter_html')['settings'], ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); Editor::create([ 'format' => 'cke4_stylescombo_span', 'editor' => 'ckeditor', @@ -404,12 +405,12 @@ class SmartDefaultSettingsTest extends KernelTestBase { ], ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); FilterFormat::create([ 'format' => 'cke4_contrib_plugins_now_in_core', 'name' => 'All CKEditor 4 contrib plugins now in core', - ])->setSyncing(TRUE)->save(); + ])->save(); Editor::create([ 'format' => 'cke4_contrib_plugins_now_in_core', 'editor' => 'ckeditor', @@ -470,7 +471,7 @@ class SmartDefaultSettingsTest extends KernelTestBase { ], ], ], - ])->setSyncing(TRUE)->save(); + ])->save(); } /** diff --git a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterMissingContentTest.php b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterMissingContentTest.php index b81b679f329..d542d9a83d1 100644 --- a/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterMissingContentTest.php +++ b/core/tests/Drupal/KernelTests/Core/Config/ConfigImporterMissingContentTest.php @@ -2,17 +2,36 @@ namespace Drupal\KernelTests\Core\Config; +use Drupal\block_content\Entity\BlockContent; +use Drupal\block_content\Entity\BlockContentType; +use Drupal\Component\Plugin\PluginBase; +use Drupal\Component\Render\FormattableMarkup; +use Drupal\Component\Render\PlainTextOutput; +use Drupal\Core\Block\Plugin\Block\Broken; use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\StorageComparer; +use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Logger\RfcLoggerTrait; use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\block\Traits\BlockCreationTrait; +use Psr\Log\LoggerInterface; /** * Tests importing configuration which has missing content dependencies. * * @group config */ -class ConfigImporterMissingContentTest extends KernelTestBase { +class ConfigImporterMissingContentTest extends KernelTestBase implements LoggerInterface { + use BlockCreationTrait; + use RfcLoggerTrait; + + /** + * The logged messages. + * + * @var string[] + */ + protected $logMessages = []; /** * Config Importer object used for testing. @@ -34,6 +53,15 @@ class ConfigImporterMissingContentTest extends KernelTestBase { 'config_import_test', ]; + /** + * {@inheritdoc} + */ + public function register(ContainerBuilder $container) { + parent::register($container); + $container->register('logger.ConfigImporterMissingContentTest', __CLASS__)->addTag('logger'); + $container->set('logger.ConfigImporterMissingContentTest', $this); + } + /** * {@inheritdoc} */ @@ -107,4 +135,64 @@ class ConfigImporterMissingContentTest extends KernelTestBase { $this->assertEquals([$entity_one->getConfigDependencyName(), $entity_two->getConfigDependencyName(), $entity_three->getConfigDependencyName()], $original_dynamic_data['dependencies']['content'], 'The imported configuration entity has the missing content entity dependency.'); } + /** + * Tests the missing content, config import and the block plugin manager. + * + * @see \Drupal\Core\Config\ConfigImporter::processMissingContent() + * @see \Drupal\config_import_test\EventSubscriber + */ + public function testMissingBlockContent() { + $this->enableModules([ + 'block', + 'block_content', + ]); + $this->container->get('theme_installer')->install(['stark']); + $this->installEntitySchema('block_content'); + // Create a block content type. + $block_content_type = BlockContentType::create([ + 'id' => 'test', + 'label' => 'Test block content', + 'description' => "Provides a block type", + ]); + $block_content_type->save(); + // And a block content entity. + $block_content = BlockContent::create([ + 'info' => 'Prototype', + 'type' => 'test', + // Set the UUID to make asserting against missing test easy. + 'uuid' => '6376f337-fcbf-4b28-b30e-ed5b6932e692', + ]); + $block_content->save(); + $plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid(); + $block = $this->placeBlock($plugin_id); + + $storage = $this->container->get('config.storage'); + $sync = $this->container->get('config.storage.sync'); + + $this->copyConfig($storage, $sync); + + $block->delete(); + $block_content->delete(); + $block_content_type->delete(); + + // Import. + $this->logMessages = []; + $config_importer = $this->configImporter(); + $config_importer->import(); + $this->assertNotContains('The "block_content:6376f337-fcbf-4b28-b30e-ed5b6932e692" was not found', $this->logMessages); + + // Ensure the expected message is generated when creating an instance of the + // block. + $instance = $this->container->get('plugin.manager.block')->createInstance($plugin_id); + $this->assertContains('The "block_content:6376f337-fcbf-4b28-b30e-ed5b6932e692" was not found', $this->logMessages); + $this->assertInstanceOf(Broken::class, $instance); + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = []): void { + $this->logMessages[] = PlainTextOutput::renderFromHtml(new FormattableMarkup($message, $context)); + } + } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index c1dac0ec459..16fa33ca7a3 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -18,6 +18,7 @@ use Drupal\Tests\Core\Config\Entity\Fixtures\ConfigEntityBaseWithPluginCollectio use Drupal\Tests\Core\Plugin\Fixtures\TestConfigurablePlugin; use Drupal\Tests\UnitTestCase; use Drupal\TestTools\Random; +use Prophecy\Argument; /** * @coversDefaultClass \Drupal\Core\Config\Entity\ConfigEntityBase @@ -354,23 +355,23 @@ class ConfigEntityBaseUnitTest extends UnitTestCase { $instance = new TestConfigurablePlugin([], $instance_id, []); $plugin_manager = $this->prophesize(PluginManagerInterface::class); - $plugin_manager->createInstance($instance_id, ['id' => $instance_id])->willReturn($instance); + $plugin_manager->createInstance($instance_id, Argument::any())->willReturn($instance); // Also set up a container with the plugin manager so that we can assert // that the plugin manager itself is also not serialized. $container = TestKernel::setContainerWithKernel(); $container->set('plugin.manager.foo', $plugin_manager->reveal()); - $entity_values = ['the_plugin_collection_config' => [$instance_id => ['foo' => 'original_value']]]; + $entity_values = ['the_plugin_collection_config' => [$instance_id => ['id' => $instance_id, 'foo' => 'original_value']]]; $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId); $entity->setPluginManager($plugin_manager->reveal()); // After creating the entity, change the plugin configuration. - $instance->setConfiguration(['foo' => 'new_value']); + $instance->setConfiguration(['id' => $instance_id, 'foo' => 'new_value']); // After changing the plugin configuration, the entity still has the // original value. - $expected_plugin_config = [$instance_id => ['foo' => 'original_value']]; + $expected_plugin_config = [$instance_id => ['id' => $instance_id, 'foo' => 'original_value']]; $this->assertSame($expected_plugin_config, $entity->get('the_plugin_collection_config')); // Ensure the plugin collection and manager is not stored. @@ -379,7 +380,7 @@ class ConfigEntityBaseUnitTest extends UnitTestCase { $this->assertNotContains('pluginManager', $vars); $this->assertSame(['pluginManager' => 'plugin.manager.foo'], $entity->get('_serviceIds')); - $expected_plugin_config = [$instance_id => ['foo' => 'new_value']]; + $expected_plugin_config = [$instance_id => ['id' => $instance_id, 'foo' => 'new_value']]; // Ensure the updated values are stored in the entity. $this->assertSame($expected_plugin_config, $entity->get('the_plugin_collection_config')); } @@ -636,6 +637,85 @@ class ConfigEntityBaseUnitTest extends UnitTestCase { $this->entity->toArray(); } + /** + * @covers ::set + * @dataProvider providerTestSetAndPreSaveWithPluginCollections + */ + public function testSetWithPluginCollections(bool $syncing, string $expected_value) { + $instance_id = 'the_instance_id'; + $instance = new TestConfigurablePlugin(['foo' => 'original_value'], $instance_id, []); + + $plugin_manager = $this->prophesize(PluginManagerInterface::class); + if ($syncing) { + $plugin_manager->createInstance(Argument::cetera())->shouldNotBeCalled(); + } + else { + $plugin_manager->createInstance($instance_id, Argument::any())->willReturn($instance); + } + + $entity_values = ['the_plugin_collection_config' => [$instance_id => ['id' => $instance_id, 'foo' => 'original_value']]]; + $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId); + $entity->setSyncing($syncing); + $entity->setPluginManager($plugin_manager->reveal()); + + // After creating the entity, change the configuration using the entity. + $entity->set('the_plugin_collection_config', [$instance_id => ['id' => $instance_id, 'foo' => 'new_value']]); + + $this->assertSame($expected_value, $instance->getConfiguration()['foo']); + } + + /** + * @covers ::preSave + * @dataProvider providerTestSetAndPreSaveWithPluginCollections + */ + public function testPreSaveWithPluginCollections(bool $syncing, string $expected_value) { + $instance_id = 'the_instance_id'; + $instance = new TestConfigurablePlugin(['foo' => 'original_value'], $instance_id, ['provider' => 'core']); + + $plugin_manager = $this->prophesize(PluginManagerInterface::class); + if ($syncing) { + $plugin_manager->createInstance(Argument::cetera())->shouldNotBeCalled(); + } + else { + $plugin_manager->createInstance($instance_id, Argument::any())->willReturn($instance); + } + + $entity_values = ['the_plugin_collection_config' => [$instance_id => ['id' => $instance_id, 'foo' => 'original_value']]]; + $entity = new TestConfigEntityWithPluginCollections($entity_values, $this->entityTypeId); + $entity->setSyncing($syncing); + $entity->setPluginManager($plugin_manager->reveal()); + + // After creating the entity, change the plugin configuration. + $instance->setConfiguration(['foo' => 'new_value']); + + $query = $this->createMock('\Drupal\Core\Entity\Query\QueryInterface'); + $storage = $this->createMock('\Drupal\Core\Config\Entity\ConfigEntityStorageInterface'); + + $query->expects($this->any()) + ->method('execute') + ->willReturn([]); + $query->expects($this->any()) + ->method('condition') + ->willReturn($query); + $storage->expects($this->any()) + ->method('getQuery') + ->willReturn($query); + $storage->expects($this->any()) + ->method('loadUnchanged') + ->willReturn($entity); + + $entity->preSave($storage); + + $this->assertSame($expected_value, $entity->get('the_plugin_collection_config')[$instance_id]['foo']); + } + + public function providerTestSetAndPreSaveWithPluginCollections(): array { + return [ + 'Not syncing' => [FALSE, 'new_value'], + 'Syncing' => [TRUE, 'original_value'], + ]; + } + } class TestConfigEntityWithPluginCollections extends ConfigEntityBaseWithPluginCollections { @@ -644,7 +724,7 @@ class TestConfigEntityWithPluginCollections extends ConfigEntityBaseWithPluginCo protected $pluginManager; - protected $the_plugin_collection_config; + protected array $the_plugin_collection_config = []; public function setPluginManager(PluginManagerInterface $plugin_manager) { $this->pluginManager = $plugin_manager; @@ -655,7 +735,7 @@ class TestConfigEntityWithPluginCollections extends ConfigEntityBaseWithPluginCo */ public function getPluginCollections() { if (!$this->pluginCollection) { - $this->pluginCollection = new DefaultLazyPluginCollection($this->pluginManager, ['the_instance_id' => ['id' => 'the_instance_id']]); + $this->pluginCollection = new DefaultLazyPluginCollection($this->pluginManager, $this->the_plugin_collection_config); } return ['the_plugin_collection_config' => $this->pluginCollection]; }