Initial Drupal 11 with DDEV setup
This commit is contained in:
		
							
								
								
									
										245
									
								
								web/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								web/core/tests/Drupal/KernelTests/Config/DefaultConfigTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,245 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Entity\ConfigEntityDependency;
 | 
			
		||||
use Drupal\Core\Config\FileStorage;
 | 
			
		||||
use Drupal\Core\Config\InstallStorage;
 | 
			
		||||
use Drupal\Core\Config\StorageInterface;
 | 
			
		||||
use Drupal\Core\Extension\ExtensionLifecycle;
 | 
			
		||||
use Drupal\KernelTests\AssertConfigTrait;
 | 
			
		||||
use Drupal\KernelTests\FileSystemModuleDiscoveryDataProviderTrait;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests that the installed config matches the default config.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Config
 | 
			
		||||
 * @group #slow
 | 
			
		||||
 */
 | 
			
		||||
class DefaultConfigTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use AssertConfigTrait;
 | 
			
		||||
  use FileSystemModuleDiscoveryDataProviderTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $timeLimit = 500;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'user', 'path_alias'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The following config entries are changed on module install.
 | 
			
		||||
   *
 | 
			
		||||
   * Comparing them does not make sense.
 | 
			
		||||
   *
 | 
			
		||||
   * @var array
 | 
			
		||||
   */
 | 
			
		||||
  public static $skippedConfig = [
 | 
			
		||||
    'locale.settings' => ['path: '],
 | 
			
		||||
    'syslog.settings' => ['facility: '],
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if installed config is equal to the exported config.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider moduleListDataProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testModuleConfig(string $module): void {
 | 
			
		||||
    $this->assertExtensionConfig($module, 'module');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests if installed config is equal to the exported config.
 | 
			
		||||
   *
 | 
			
		||||
   * @dataProvider themeListDataProvider
 | 
			
		||||
   */
 | 
			
		||||
  public function testThemeConfig($theme): void {
 | 
			
		||||
    $this->assertExtensionConfig($theme, 'theme');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that the config provided by the extension is correct.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $name
 | 
			
		||||
   *   Extension name.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   Extension type, either 'module' or 'theme'.
 | 
			
		||||
   *
 | 
			
		||||
   * @internal
 | 
			
		||||
   */
 | 
			
		||||
  protected function assertExtensionConfig(string $name, string $type): void {
 | 
			
		||||
    // Parse .info.yml file for module/theme $name. Since it's not installed at
 | 
			
		||||
    // this point we can't retrieve it from the 'module_handler' service.
 | 
			
		||||
    switch ($name) {
 | 
			
		||||
      case 'test_deprecated_theme':
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/modules/system/tests/themes/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'deprecated_module':
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/modules/system/tests/modules/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        $file_name = DRUPAL_ROOT . '/core/' . $type . 's/' . $name . '/' . $name . '.info.yml';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    $info = \Drupal::service('info_parser')->parse($file_name);
 | 
			
		||||
    // Test we have a parsed info.yml file.
 | 
			
		||||
    $this->assertNotEmpty($info);
 | 
			
		||||
 | 
			
		||||
    // Skip deprecated extensions.
 | 
			
		||||
    if (isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER])
 | 
			
		||||
      && $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] === ExtensionLifecycle::DEPRECATED) {
 | 
			
		||||
      $this->markTestSkipped("The $type '$name' is deprecated.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // System and user are required in order to be able to install some of the
 | 
			
		||||
    // other modules. Therefore they are put into static::$modules, which though
 | 
			
		||||
    // doesn't install config files, so import those config files explicitly. Do
 | 
			
		||||
    // this for all tests in case optional configuration depends on it.
 | 
			
		||||
    $this->installConfig(['system', 'user']);
 | 
			
		||||
 | 
			
		||||
    $extension_path = \Drupal::service('extension.path.resolver')->getPath($type, $name) . '/';
 | 
			
		||||
    $extension_config_storage = new FileStorage($extension_path . InstallStorage::CONFIG_INSTALL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
 | 
			
		||||
    $optional_config_storage = new FileStorage($extension_path . InstallStorage::CONFIG_OPTIONAL_DIRECTORY, StorageInterface::DEFAULT_COLLECTION);
 | 
			
		||||
 | 
			
		||||
    if (empty($optional_config_storage->listAll()) && empty($extension_config_storage->listAll())) {
 | 
			
		||||
      $this->markTestSkipped("$name has no configuration to test");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Work out any additional modules and themes that need installing to create
 | 
			
		||||
    // an optional config.
 | 
			
		||||
    $modules_to_install = $type !== 'theme' ? [$name] : [];
 | 
			
		||||
    $themes_to_install = $type === 'theme' ? [$name] : [];
 | 
			
		||||
    foreach ($optional_config_storage->listAll() as $config_name) {
 | 
			
		||||
      $data = $optional_config_storage->read($config_name);
 | 
			
		||||
      $dependency = new ConfigEntityDependency($config_name, $data);
 | 
			
		||||
      $modules_to_install = array_merge($modules_to_install, $dependency->getDependencies('module'));
 | 
			
		||||
      $themes_to_install = array_merge($themes_to_install, $dependency->getDependencies('theme'));
 | 
			
		||||
    }
 | 
			
		||||
    // Remove core and standard because they cannot be installed.
 | 
			
		||||
    $modules_to_install = array_diff(array_unique($modules_to_install), ['core', 'standard']);
 | 
			
		||||
    $this->container->get('module_installer')->install($modules_to_install);
 | 
			
		||||
    $this->container->get('theme_installer')->install(array_unique($themes_to_install));
 | 
			
		||||
 | 
			
		||||
    // Test configuration in the extension's config/install directory.
 | 
			
		||||
    $this->doTestsOnConfigStorage($extension_config_storage, $name, $type);
 | 
			
		||||
 | 
			
		||||
    // Test configuration in the extension's config/optional directory.
 | 
			
		||||
    $this->doTestsOnConfigStorage($optional_config_storage, $name, $type);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A data provider that lists every theme in core.
 | 
			
		||||
   *
 | 
			
		||||
   * Also adds a deprecated theme with config.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   An array of theme names to test, with both key and value being the name
 | 
			
		||||
   *   of the theme.
 | 
			
		||||
   */
 | 
			
		||||
  public static function themeListDataProvider() {
 | 
			
		||||
    $prefix = dirname(__DIR__, 4) . DIRECTORY_SEPARATOR . 'themes';
 | 
			
		||||
    $theme_dirs = array_keys(iterator_to_array(new \FilesystemIterator($prefix)));
 | 
			
		||||
    $theme_names = array_map(function ($path) use ($prefix) {
 | 
			
		||||
      return str_replace($prefix . DIRECTORY_SEPARATOR, '', $path);
 | 
			
		||||
    }, $theme_dirs);
 | 
			
		||||
    $themes_keyed = array_combine($theme_names, $theme_names);
 | 
			
		||||
 | 
			
		||||
    // Engines is not a theme.
 | 
			
		||||
    unset($themes_keyed['engines']);
 | 
			
		||||
 | 
			
		||||
    // Add a deprecated theme with config.
 | 
			
		||||
    $themes_keyed['test_deprecated_theme'] = 'test_deprecated_theme';
 | 
			
		||||
 | 
			
		||||
    return array_map(function ($theme) {
 | 
			
		||||
      return [$theme];
 | 
			
		||||
    }, $themes_keyed);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A data provider that lists every module in core.
 | 
			
		||||
   *
 | 
			
		||||
   * Also adds a deprecated module with config.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string[][]
 | 
			
		||||
   *   An array of module names to test, with both key and value being the name
 | 
			
		||||
   *   of the module.
 | 
			
		||||
   */
 | 
			
		||||
  public static function moduleListDataProvider(): array {
 | 
			
		||||
    $modules_keyed = self::coreModuleListDataProvider();
 | 
			
		||||
 | 
			
		||||
    // Add a deprecated module with config.
 | 
			
		||||
    $modules_keyed['deprecated_module'] = ['deprecated_module'];
 | 
			
		||||
 | 
			
		||||
    return $modules_keyed;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that default config matches the installed config.
 | 
			
		||||
   *
 | 
			
		||||
   * @param \Drupal\Core\Config\StorageInterface $default_config_storage
 | 
			
		||||
   *   The default config storage to test.
 | 
			
		||||
   * @param string $extension
 | 
			
		||||
   *   The extension that is being tested.
 | 
			
		||||
   * @param string $type
 | 
			
		||||
   *   The extension type to test.
 | 
			
		||||
   */
 | 
			
		||||
  protected function doTestsOnConfigStorage(StorageInterface $default_config_storage, $extension, string $type = 'module'): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigManagerInterface $config_manager */
 | 
			
		||||
    $config_manager = $this->container->get('config.manager');
 | 
			
		||||
 | 
			
		||||
    // Just connect directly to the config table so we don't need to worry about
 | 
			
		||||
    // the cache layer.
 | 
			
		||||
    $active_config_storage = $this->container->get('config.storage');
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */
 | 
			
		||||
    $config_factory = $this->container->get('config.factory');
 | 
			
		||||
 | 
			
		||||
    foreach ($default_config_storage->listAll() as $config_name) {
 | 
			
		||||
      if ($active_config_storage->exists($config_name)) {
 | 
			
		||||
        // If it is a config entity re-save it. This ensures that any
 | 
			
		||||
        // recalculation of dependencies does not cause config change.
 | 
			
		||||
        if ($entity_type = $config_manager->getEntityTypeIdByName($config_name)) {
 | 
			
		||||
          $entity_storage = $config_manager
 | 
			
		||||
            ->getEntityTypeManager()
 | 
			
		||||
            ->getStorage($entity_type);
 | 
			
		||||
          $id = $entity_storage->getIDFromConfigName($config_name, $entity_storage->getEntityType()
 | 
			
		||||
            ->getConfigPrefix());
 | 
			
		||||
          $entity_storage->load($id)->calculateDependencies()->save();
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          // Ensure simple configuration is re-saved so any schema sorting is
 | 
			
		||||
          // applied.
 | 
			
		||||
          $config_factory->getEditable($config_name)->save();
 | 
			
		||||
        }
 | 
			
		||||
        $result = $config_manager->diff($default_config_storage, $active_config_storage, $config_name);
 | 
			
		||||
        // ::assertConfigDiff will throw an exception if the configuration is
 | 
			
		||||
        // different.
 | 
			
		||||
        $this->assertNull($this->assertConfigDiff($result, $config_name, static::$skippedConfig));
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        $data = $default_config_storage->read($config_name);
 | 
			
		||||
        $dependency = new ConfigEntityDependency($config_name, $data);
 | 
			
		||||
        if ($dependency->hasDependency('module', 'standard')) {
 | 
			
		||||
          // Skip configuration with a dependency on the standard profile. Such
 | 
			
		||||
          // configuration has probably been removed from the standard profile
 | 
			
		||||
          // and needs its own test.
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
        $info = $this->container->get("extension.list.$type")->getExtensionInfo($extension);
 | 
			
		||||
        if (!isset($info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER]) || $info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER] !== ExtensionLifecycle::EXPERIMENTAL) {
 | 
			
		||||
          $this->fail("$config_name provided by $extension does not exist after installing all dependencies");
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										564
									
								
								web/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										564
									
								
								web/core/tests/Drupal/KernelTests/Config/Schema/MappingTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,564 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config\Schema;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore childkey
 | 
			
		||||
 | 
			
		||||
use Drupal\block\Entity\Block;
 | 
			
		||||
use Drupal\Core\Config\Schema\Mapping;
 | 
			
		||||
use Drupal\Core\TypedData\MapDataDefinition;
 | 
			
		||||
use Drupal\editor\Entity\Editor;
 | 
			
		||||
use Drupal\field\Entity\FieldConfig;
 | 
			
		||||
use Drupal\filter\Entity\FilterFormat;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Config\Schema\Mapping
 | 
			
		||||
 * @group Config
 | 
			
		||||
 */
 | 
			
		||||
class MappingTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $configSchemaCheckerExclusions = [
 | 
			
		||||
    'config_schema_deprecated_test.settings',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @dataProvider providerMappingInterpretation
 | 
			
		||||
   */
 | 
			
		||||
  public function testMappingInterpretation(
 | 
			
		||||
    string $config_name,
 | 
			
		||||
    ?string $property_path,
 | 
			
		||||
    array $expected_valid_keys,
 | 
			
		||||
    array $expected_optional_keys,
 | 
			
		||||
    array $expected_dynamically_valid_keys,
 | 
			
		||||
  ): void {
 | 
			
		||||
    // Some config needs some dependencies installed.
 | 
			
		||||
    switch ($config_name) {
 | 
			
		||||
      case 'block.block.branding':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => 'branding',
 | 
			
		||||
          'plugin' => 'system_branding_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'use_site_logo' => TRUE,
 | 
			
		||||
            'use_site_name' => TRUE,
 | 
			
		||||
            'use_site_slogan' => TRUE,
 | 
			
		||||
            'label_display' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'block.block.local_tasks':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => 'local_tasks',
 | 
			
		||||
          'plugin' => 'local_tasks_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'primary' => TRUE,
 | 
			
		||||
            'secondary' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'block.block.positively_powered___alternate_reality_with_fallback_type___':
 | 
			
		||||
        $this->enableModules(['config_schema_add_fallback_type_test']);
 | 
			
		||||
        $id = 'positively_powered___alternate_reality_with_fallback_type___';
 | 
			
		||||
      case 'block.block.positively_powered':
 | 
			
		||||
        $this->enableModules(['system', 'block']);
 | 
			
		||||
        /** @var \Drupal\Core\Extension\ThemeInstallerInterface $theme_installer */
 | 
			
		||||
        $theme_installer = $this->container->get('theme_installer');
 | 
			
		||||
        $theme_installer->install(['stark']);
 | 
			
		||||
        Block::create([
 | 
			
		||||
          'id' => $id ?? 'positively_powered',
 | 
			
		||||
          'plugin' => 'system_powered_by_block',
 | 
			
		||||
          'theme' => 'stark',
 | 
			
		||||
          'status' => TRUE,
 | 
			
		||||
          'settings' => [
 | 
			
		||||
            'label_display' => FALSE,
 | 
			
		||||
            // This is inherited from `type: block_settings`.
 | 
			
		||||
            'context_mapping' => [],
 | 
			
		||||
          ],
 | 
			
		||||
          // Avoid showing "Powered by Drupal" on 404 responses.
 | 
			
		||||
          'visibility' => [
 | 
			
		||||
            'I_CAN_CHOOSE_THIS' => [
 | 
			
		||||
              // This is what determines the
 | 
			
		||||
              'id' => 'response_status',
 | 
			
		||||
              'negate' => FALSE,
 | 
			
		||||
              'status_codes' => [
 | 
			
		||||
                404,
 | 
			
		||||
              ],
 | 
			
		||||
            ],
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'config_schema_deprecated_test.settings':
 | 
			
		||||
        $this->enableModules(['config_schema_deprecated_test']);
 | 
			
		||||
        $config = $this->config('config_schema_deprecated_test.settings');
 | 
			
		||||
        // @see \Drupal\KernelTests\Core\Config\ConfigSchemaDeprecationTest
 | 
			
		||||
        $config
 | 
			
		||||
          ->set('complex_structure_deprecated.type', 'fruits')
 | 
			
		||||
          ->set('complex_structure_deprecated.products', ['apricot', 'apple'])
 | 
			
		||||
          ->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'editor.editor.funky':
 | 
			
		||||
        $this->enableModules(['filter', 'editor', 'ckeditor5']);
 | 
			
		||||
        FilterFormat::create(['format' => 'funky', 'name' => 'Funky'])->save();
 | 
			
		||||
        Editor::create([
 | 
			
		||||
          'format' => 'funky',
 | 
			
		||||
          'editor' => 'ckeditor5',
 | 
			
		||||
          'image_upload' => [
 | 
			
		||||
            'status' => FALSE,
 | 
			
		||||
          ],
 | 
			
		||||
        ])->save();
 | 
			
		||||
        break;
 | 
			
		||||
 | 
			
		||||
      case 'field.field.node.config_mapping_test.comment_config_mapping_test':
 | 
			
		||||
        $this->enableModules(['user', 'field', 'node', 'comment', 'taxonomy', 'config_mapping_test']);
 | 
			
		||||
        $this->installEntitySchema('user');
 | 
			
		||||
        $this->installEntitySchema('node');
 | 
			
		||||
        $this->assertNull(FieldConfig::load('node.config_mapping_test.comment_config_mapping_test'));
 | 
			
		||||
        // \Drupal\node\Entity\NodeType::$preview_mode uses DRUPAL_OPTIONAL,
 | 
			
		||||
        // which is defined in system.module.
 | 
			
		||||
        require_once 'core/modules/system/system.module';
 | 
			
		||||
        $this->installConfig(['config_mapping_test']);
 | 
			
		||||
        $this->assertNotNull(FieldConfig::load('node.config_mapping_test.comment_config_mapping_test'));
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    $mapping = $typed_config_manager->get($config_name);
 | 
			
		||||
    if ($property_path) {
 | 
			
		||||
      $mapping = $mapping->get($property_path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assert($mapping instanceof Mapping);
 | 
			
		||||
    $expected_required_keys = array_values(array_diff($expected_valid_keys, $expected_optional_keys));
 | 
			
		||||
    $this->assertSame($expected_valid_keys, $mapping->getValidKeys());
 | 
			
		||||
    $this->assertSame($expected_required_keys, $mapping->getRequiredKeys());
 | 
			
		||||
    $this->assertSame($expected_dynamically_valid_keys, $mapping->getDynamicallyValidKeys());
 | 
			
		||||
    $this->assertSame($expected_optional_keys, $mapping->getOptionalKeys());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides test cases for all kinds of i) dynamic typing, ii) optional keys.
 | 
			
		||||
   *
 | 
			
		||||
   * @see https://www.drupal.org/files/ConfigSchemaCheatSheet2.0.pdf
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Generator
 | 
			
		||||
   *   The test cases.
 | 
			
		||||
   */
 | 
			
		||||
  public static function providerMappingInterpretation(): \Generator {
 | 
			
		||||
    $available_block_settings_types = [
 | 
			
		||||
      'block.settings.field_block:*:*:*' => [
 | 
			
		||||
        'formatter',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.extra_field_block:*:*:*' => [
 | 
			
		||||
        'formatter',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.system_branding_block' => [
 | 
			
		||||
        'use_site_logo',
 | 
			
		||||
        'use_site_name',
 | 
			
		||||
        'use_site_slogan',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.system_menu_block:*' => [
 | 
			
		||||
        'level',
 | 
			
		||||
        'depth',
 | 
			
		||||
        'expand_all_items',
 | 
			
		||||
      ],
 | 
			
		||||
      'block.settings.local_tasks_block' => [
 | 
			
		||||
        'primary',
 | 
			
		||||
        'secondary',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // A simple config often is just a single Mapping object.
 | 
			
		||||
    yield 'No dynamic type: core.extension' => [
 | 
			
		||||
      'core.extension',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_object`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        // Keys defined locally, in `type: core.extension`.
 | 
			
		||||
        // @see core/config/schema/core.extension.schema.yml
 | 
			
		||||
        'module',
 | 
			
		||||
        'theme',
 | 
			
		||||
        'profile',
 | 
			
		||||
      ],
 | 
			
		||||
      [
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        'profile',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Special case: deprecated  is needed for deprecated config schema:
 | 
			
		||||
    // - deprecated keys are treated as optional
 | 
			
		||||
    // - if a deprecated property path is itself a mapping, then the keys inside
 | 
			
		||||
    //   are not optional
 | 
			
		||||
    yield 'No dynamic type: config_schema_deprecated_test.settings' => [
 | 
			
		||||
      'config_schema_deprecated_test.settings',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_object`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        '_core',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // config_schema_deprecated_test.settings`.
 | 
			
		||||
        // @see core/modules/config/tests/config_schema_deprecated_test/config/schema/config_schema_deprecated_test.schema.yml
 | 
			
		||||
        'complex_structure_deprecated',
 | 
			
		||||
      ],
 | 
			
		||||
      ['_core', 'langcode', 'complex_structure_deprecated'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'No dynamic type: config_schema_deprecated_test.settings:complex_structure_deprecated' => [
 | 
			
		||||
      'config_schema_deprecated_test.settings',
 | 
			
		||||
      'complex_structure_deprecated',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // config_schema_deprecated_test.settings`.
 | 
			
		||||
        // @see core/modules/config/tests/config_schema_deprecated_test/config/schema/config_schema_deprecated_test.schema.yml
 | 
			
		||||
        'type',
 | 
			
		||||
        'products',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // A config entity is always a Mapping at the top level, but most nesting is
 | 
			
		||||
    // also using Mappings (unless the keys are free to be chosen, then a
 | 
			
		||||
    // Sequence would be used).
 | 
			
		||||
    yield 'No dynamic type: block.block.branding' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      NULL,
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_entity`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'langcode',
 | 
			
		||||
        'status',
 | 
			
		||||
        'dependencies',
 | 
			
		||||
        'third_party_settings',
 | 
			
		||||
        '_core',
 | 
			
		||||
        // Keys defined locally, in `type: block.block.*`.
 | 
			
		||||
        // @see core/modules/block/config/schema/block.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'theme',
 | 
			
		||||
        'region',
 | 
			
		||||
        'weight',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'plugin',
 | 
			
		||||
        'settings',
 | 
			
		||||
        'visibility',
 | 
			
		||||
      ],
 | 
			
		||||
      ['third_party_settings', '_core'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of nested Mapping objects in config entities.
 | 
			
		||||
    yield 'No dynamic type: block.block.branding:dependencies' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      'dependencies',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: config_dependencies_base`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'config',
 | 
			
		||||
        'content',
 | 
			
		||||
        'module',
 | 
			
		||||
        'theme',
 | 
			
		||||
        // Keys defined locally, in `type: config_dependencies`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'enforced',
 | 
			
		||||
      ],
 | 
			
		||||
      // All these keys are optional!
 | 
			
		||||
      ['config', 'content', 'module', 'theme', 'enforced'],
 | 
			
		||||
      [],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // Three examples of `[%parent]`-based dynamic typing in config schema, and
 | 
			
		||||
    // the consequences on what keys are considered valid: the first 2 depend
 | 
			
		||||
    // on the block plugin being used using a single `%parent`, the third
 | 
			
		||||
    // depends on the field plugin being used using a double `%parent`.
 | 
			
		||||
    // See `type: block.block.*` which uses
 | 
			
		||||
    // `type: block.settings.[%parent.plugin]`, and `type: field_config_base`
 | 
			
		||||
    // which uses `type: field.value.[%parent.%parent.field_type]`.
 | 
			
		||||
    yield 'Dynamic type with [%parent]: block.block.branding:settings' => [
 | 
			
		||||
      'block.block.branding',
 | 
			
		||||
      'settings',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: block.settings.*`, which in turn is
 | 
			
		||||
        // inherited from `type: block_settings`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'label',
 | 
			
		||||
        'label_display',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type:
 | 
			
		||||
        // block.settings.system_branding_block`.
 | 
			
		||||
        // @see core/modules/block/config/schema/block.schema.yml
 | 
			
		||||
        ...$available_block_settings_types['block.settings.system_branding_block'],
 | 
			
		||||
      ],
 | 
			
		||||
      // This key is optional, see `type: block_settings`.
 | 
			
		||||
      // @see core.data_types.schema.yml
 | 
			
		||||
      ['context_mapping'],
 | 
			
		||||
      $available_block_settings_types,
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'Dynamic type with [%parent]: block.block.local_tasks:settings' => [
 | 
			
		||||
      'block.block.local_tasks',
 | 
			
		||||
      'settings',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: block.settings.*`, which in turn is
 | 
			
		||||
        // inherited from `type: block_settings`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'label',
 | 
			
		||||
        'label_display',
 | 
			
		||||
        'provider',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: block.settings.local_tasks_block`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        ...$available_block_settings_types['block.settings.local_tasks_block'],
 | 
			
		||||
      ],
 | 
			
		||||
      // This key is optional, see `type: block_settings`.
 | 
			
		||||
      // @see core.data_types.schema.yml
 | 
			
		||||
      ['context_mapping'],
 | 
			
		||||
      $available_block_settings_types,
 | 
			
		||||
    ];
 | 
			
		||||
    yield 'Dynamic type with [%parent.%parent]: field.field.node.config_mapping_test.comment_config_mapping_test:default_value.0' => [
 | 
			
		||||
      'field.field.node.config_mapping_test.comment_config_mapping_test',
 | 
			
		||||
      'default_value.0',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type: field.value.comment`.
 | 
			
		||||
        // @see core/modules/comment/config/schema/comment.schema.yml
 | 
			
		||||
        'status',
 | 
			
		||||
        'cid',
 | 
			
		||||
        'last_comment_timestamp',
 | 
			
		||||
        'last_comment_name',
 | 
			
		||||
        'last_comment_uid',
 | 
			
		||||
        'comment_count',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [
 | 
			
		||||
        'field.value.string' => ['value'],
 | 
			
		||||
        'field.value.string_long' => ['value'],
 | 
			
		||||
        'field.value.uri' => ['value'],
 | 
			
		||||
        'field.value.created' => ['value'],
 | 
			
		||||
        'field.value.changed' => ['value'],
 | 
			
		||||
        'field.value.entity_reference' => ['target_id', 'target_uuid'],
 | 
			
		||||
        'field.value.boolean' => ['value'],
 | 
			
		||||
        'field.value.email' => ['value'],
 | 
			
		||||
        'field.value.integer' => ['value'],
 | 
			
		||||
        'field.value.decimal' => ['value'],
 | 
			
		||||
        'field.value.float' => ['value'],
 | 
			
		||||
        'field.value.timestamp' => ['value'],
 | 
			
		||||
        'field.value.language' => ['value'],
 | 
			
		||||
        'field.value.comment' => [
 | 
			
		||||
          'status',
 | 
			
		||||
          'cid',
 | 
			
		||||
          'last_comment_timestamp',
 | 
			
		||||
          'last_comment_name',
 | 
			
		||||
          'last_comment_uid',
 | 
			
		||||
          'comment_count',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of `[childkey]`-based dynamic mapping typing in config schema,
 | 
			
		||||
    // for a mapping inside a sequence: the `id` key-value pair in the mapping
 | 
			
		||||
    // determines the type of the mapping. The key in the sequence whose value
 | 
			
		||||
    // is the mapping is irrelevant, it can be arbitrarily chosen.
 | 
			
		||||
    // See `type: block.block.*` which uses `type: condition.plugin.[id]`.
 | 
			
		||||
    yield 'Dynamic type with [childkey]: block.block.positively_powered:visibility.I_CAN_CHOOSE_THIS' => [
 | 
			
		||||
      'block.block.positively_powered',
 | 
			
		||||
      'visibility.I_CAN_CHOOSE_THIS',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: condition.plugin`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'negate',
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: condition.plugin.response_status`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        'status_codes',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      // Note the presence of `id`, `negate`, `uuid` and `context_mapping` here.
 | 
			
		||||
      // That's because there is no `condition.plugin.*` type that specifies
 | 
			
		||||
      // defaults. Each individual condition plugin has the freedom to deviate
 | 
			
		||||
      // from this approach!
 | 
			
		||||
      [
 | 
			
		||||
        'condition.plugin.entity_bundle:*' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'bundles',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.request_path' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'pages',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.response_status' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'status_codes',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.current_theme' => [
 | 
			
		||||
          'id',
 | 
			
		||||
          'negate',
 | 
			
		||||
          'uuid',
 | 
			
		||||
          'context_mapping',
 | 
			
		||||
          'theme',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    // Same, but what if `type: condition.plugin.*` would have existed?
 | 
			
		||||
    // @see core/modules/config/tests/config_schema_add_fallback_type_test/config/schema/config_schema_add_fallback_type_test.schema.yml
 | 
			
		||||
    yield 'Dynamic type with [childkey]: block.block.positively_powered___alternate_reality_with_fallback_type___:visibility' => [
 | 
			
		||||
      'block.block.positively_powered___alternate_reality_with_fallback_type___',
 | 
			
		||||
      'visibility.I_CAN_CHOOSE_THIS',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys inherited from `type: condition.plugin`.
 | 
			
		||||
        // @see core/config/schema/core.data_types.schema.yml
 | 
			
		||||
        'id',
 | 
			
		||||
        'negate',
 | 
			
		||||
        'uuid',
 | 
			
		||||
        'context_mapping',
 | 
			
		||||
        // Keys defined locally, in `type: condition.plugin.response_status`.
 | 
			
		||||
        // @see core/modules/system/config/schema/system.schema.yml
 | 
			
		||||
        'status_codes',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      // Note the ABSENCE of `id`, `negate`, `uuid` and `context_mapping`
 | 
			
		||||
      // compared to the previous test case, because now the
 | 
			
		||||
      // `condition.plugin.*` type does exist.
 | 
			
		||||
      [
 | 
			
		||||
        'condition.plugin.entity_bundle:*' => [
 | 
			
		||||
          'bundles',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.request_path' => [
 | 
			
		||||
          'pages',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.response_status' => [
 | 
			
		||||
          'status_codes',
 | 
			
		||||
        ],
 | 
			
		||||
        'condition.plugin.current_theme' => [
 | 
			
		||||
          'theme',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // An example of `[%key]`-based dynamic mapping typing in config schema: the
 | 
			
		||||
    // key in the sequence determines the type of the mapping. Unlike the above
 | 
			
		||||
    // `[childkey]` example, the key has meaning here.
 | 
			
		||||
    // See `type: editor.settings.ckeditor5`, which uses
 | 
			
		||||
    // `type: ckeditor5.plugin.[%key]`.
 | 
			
		||||
    yield 'Dynamic type with [%key]: editor.editor.funky:settings.plugins.ckeditor5_heading' => [
 | 
			
		||||
      'editor.editor.funky',
 | 
			
		||||
      'settings.plugins.ckeditor5_heading',
 | 
			
		||||
      [
 | 
			
		||||
        // Keys defined locally, in `type: ckeditor5.plugin.ckeditor5_heading`.
 | 
			
		||||
        // @see core/modules/ckeditor5/config/schema/ckeditor5.schema.yml
 | 
			
		||||
        'enabled_headings',
 | 
			
		||||
      ],
 | 
			
		||||
      [],
 | 
			
		||||
      [
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_language' => ['language_list'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_heading' => ['enabled_headings'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_imageResize' => ['allow_resize'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_sourceEditing' => ['allowed_tags'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_alignment' => ['enabled_alignments'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_list' => ['properties', 'multiBlock'],
 | 
			
		||||
        'ckeditor5.plugin.media_media' => ['allow_view_mode_override'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_codeBlock' => ['languages'],
 | 
			
		||||
        'ckeditor5.plugin.ckeditor5_style' => ['styles'],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @testWith [false, 42, "The mapping definition at `foobar` is invalid: its `invalid` key contains a integer. It must be an array."]
 | 
			
		||||
   *           [false, 10.2, "The mapping definition at `foobar` is invalid: its `invalid` key contains a double. It must be an array."]
 | 
			
		||||
   *           [false, "type", "The mapping definition at `foobar` is invalid: its `invalid` key contains a string. It must be an array."]
 | 
			
		||||
   *           [false, false, "The mapping definition at `foobar` is invalid: its `invalid` key contains a boolean. It must be an array."]
 | 
			
		||||
   *           [true, 42, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a integer. It must be an array."]
 | 
			
		||||
   *           [true, 10.2, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a double. It must be an array."]
 | 
			
		||||
   *           [true, "type", "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a string. It must be an array."]
 | 
			
		||||
   *           [true, false, "The mapping definition at `my_module.settings:foobar` is invalid: its `invalid` key contains a boolean. It must be an array."]
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidMappingKeyDefinition(bool $has_parent, mixed $invalid_key_definition, string $expected_message): void {
 | 
			
		||||
    $definition = new MapDataDefinition([
 | 
			
		||||
      'type' => 'mapping',
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'valid' => [
 | 
			
		||||
          'type' => 'boolean',
 | 
			
		||||
          'label' => 'This is a valid key-value pair in this mapping',
 | 
			
		||||
        ],
 | 
			
		||||
        'invalid' => $invalid_key_definition,
 | 
			
		||||
      ],
 | 
			
		||||
    ]);
 | 
			
		||||
    $parent = NULL;
 | 
			
		||||
    if ($has_parent) {
 | 
			
		||||
      $parent = new Mapping(
 | 
			
		||||
        new MapDataDefinition(['type' => 'mapping', 'mapping' => []]),
 | 
			
		||||
        'my_module.settings',
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage($expected_message);
 | 
			
		||||
    new Mapping($definition, 'foobar', $parent);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @testWith [true]
 | 
			
		||||
   *           [1]
 | 
			
		||||
   *           ["true"]
 | 
			
		||||
   *           [0]
 | 
			
		||||
   *           ["false"]
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidRequiredKeyFlag(mixed $required_key_flag_value): void {
 | 
			
		||||
    $this->expectException(\LogicException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The `requiredKey` flag must either be omitted or have `false` as the value.');
 | 
			
		||||
    new Mapping(new MapDataDefinition([
 | 
			
		||||
      'type' => 'mapping',
 | 
			
		||||
      'mapping' => [
 | 
			
		||||
        'something' => [
 | 
			
		||||
          'requiredKey' => $required_key_flag_value,
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ]));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										231
									
								
								web/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								web/core/tests/Drupal/KernelTests/Config/TypedConfigTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,231 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Config;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Config\Schema\Sequence;
 | 
			
		||||
use Drupal\Core\Config\Schema\SequenceDataDefinition;
 | 
			
		||||
use Drupal\Core\Config\Schema\TypedConfigInterface;
 | 
			
		||||
use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 | 
			
		||||
use Drupal\Core\TypedData\ComplexDataInterface;
 | 
			
		||||
use Drupal\Core\TypedData\Type\IntegerInterface;
 | 
			
		||||
use Drupal\Core\TypedData\Type\StringInterface;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
 | 
			
		||||
 | 
			
		||||
// cspell:ignore nyans
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests config validation mechanism.
 | 
			
		||||
 *
 | 
			
		||||
 * @group Config
 | 
			
		||||
 */
 | 
			
		||||
class TypedConfigTest extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['config_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $configSchemaCheckerExclusions = ['config_test.validation'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
 | 
			
		||||
    $this->installConfig('config_test');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Verifies that the Typed Data API is implemented correctly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testTypedDataAPI(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
 | 
			
		||||
    // Test non-existent data.
 | 
			
		||||
    try {
 | 
			
		||||
      $typed_config_manager->get('config_test.non_existent');
 | 
			
		||||
      $this->fail('Expected error when trying to get non-existent typed config.');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\InvalidArgumentException $e) {
 | 
			
		||||
      $this->assertEquals('Missing required data for typed configuration: config_test.non_existent', $e->getMessage());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
 | 
			
		||||
    // Test a primitive.
 | 
			
		||||
    $string_data = $typed_config->get('llama');
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $string_data);
 | 
			
		||||
    $this->assertEquals('llama', $string_data->getValue());
 | 
			
		||||
 | 
			
		||||
    // Test complex data.
 | 
			
		||||
    $mapping = $typed_config->get('cat');
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\ComplexDataInterface $mapping */
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataInterface::class, $mapping);
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $mapping->get('type'));
 | 
			
		||||
    $this->assertEquals('kitten', $mapping->get('type')->getValue());
 | 
			
		||||
    $this->assertInstanceOf(IntegerInterface::class, $mapping->get('count'));
 | 
			
		||||
    $this->assertEquals(2, $mapping->get('count')->getValue());
 | 
			
		||||
    // Verify the item metadata is available.
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataDefinitionInterface::class, $mapping->getDataDefinition());
 | 
			
		||||
    $this->assertArrayHasKey('type', $mapping->getProperties());
 | 
			
		||||
    $this->assertArrayHasKey('count', $mapping->getProperties());
 | 
			
		||||
 | 
			
		||||
    // Test accessing sequences.
 | 
			
		||||
    $sequence = $typed_config->get('giraffe');
 | 
			
		||||
    /** @var \Drupal\Core\TypedData\ListInterface $sequence */
 | 
			
		||||
    $this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
 | 
			
		||||
    $this->assertSame(Sequence::class, $sequence->getDataDefinition()->getClass());
 | 
			
		||||
    $this->assertSame('sequence', $sequence->getDataDefinition()->getDataType());
 | 
			
		||||
    $this->assertInstanceOf(ComplexDataInterface::class, $sequence);
 | 
			
		||||
    $this->assertInstanceOf(StringInterface::class, $sequence->get('hum1'));
 | 
			
		||||
    $this->assertEquals('hum1', $sequence->get('hum1')->getValue());
 | 
			
		||||
    $this->assertEquals('hum2', $sequence->get('hum2')->getValue());
 | 
			
		||||
    $this->assertCount(2, $sequence->getIterator());
 | 
			
		||||
    // Verify the item metadata is available.
 | 
			
		||||
    $this->assertInstanceOf(SequenceDataDefinition::class, $sequence->getDataDefinition());
 | 
			
		||||
 | 
			
		||||
    // Test accessing typed config objects for simple config and config
 | 
			
		||||
    // entities.
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    $typed_config = $typed_config_manager->createFromNameAndData('config_test.validation', \Drupal::configFactory()->get('config_test.validation')->get());
 | 
			
		||||
    $this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
 | 
			
		||||
    $this->assertEquals(['_core', 'llama', 'cat', 'giraffe', 'uuid', 'string__not_blank', 'host'], array_keys($typed_config->getElements()));
 | 
			
		||||
    $this->assertSame('config_test.validation', $typed_config->getName());
 | 
			
		||||
    $this->assertSame('config_test.validation', $typed_config->getPropertyPath());
 | 
			
		||||
    $this->assertSame('config_test.validation.llama', $typed_config->get('llama')->getPropertyPath());
 | 
			
		||||
 | 
			
		||||
    $config_test_entity = \Drupal::entityTypeManager()->getStorage('config_test')->create([
 | 
			
		||||
      'id' => 'test',
 | 
			
		||||
      'label' => 'Test',
 | 
			
		||||
      'weight' => 11,
 | 
			
		||||
      'style' => 'test_style',
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->createFromNameAndData($config_test_entity->getConfigDependencyName(), $config_test_entity->toArray());
 | 
			
		||||
    $this->assertInstanceOf(TypedConfigInterface::class, $typed_config);
 | 
			
		||||
    $this->assertEquals(['uuid', 'langcode', 'status', 'dependencies', 'id', 'label', 'weight', 'style', 'size', 'size_value', 'protected_property'], array_keys($typed_config->getElements()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests the behavior of `NotBlank` on required data.
 | 
			
		||||
   *
 | 
			
		||||
   * @testWith ["", false, "This value should not be blank."]
 | 
			
		||||
   *           ["", true, "This value should not be blank."]
 | 
			
		||||
   *           [null, false, "This value should not be blank."]
 | 
			
		||||
   *           [null, true, "This value should not be null."]
 | 
			
		||||
   *
 | 
			
		||||
   * @see \Drupal\Core\TypedData\DataDefinition::getConstraints()
 | 
			
		||||
   * @see \Drupal\Core\TypedData\DataDefinitionInterface::isRequired()
 | 
			
		||||
   * @see \Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint
 | 
			
		||||
   * @see \Symfony\Component\Validator\Constraints\NotBlank::$allowNull
 | 
			
		||||
   */
 | 
			
		||||
  public function testNotBlankInteractionWithNotNull(?string $value, bool $is_required, string $expected_message): void {
 | 
			
		||||
    \Drupal::configFactory()->getEditable('config_test.validation')
 | 
			
		||||
      ->set('string__not_blank', $value)
 | 
			
		||||
      ->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = \Drupal::service('config.typed')->get('config_test.validation');
 | 
			
		||||
    $typed_config->get('string__not_blank')->getDataDefinition()->setRequired($is_required);
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
 | 
			
		||||
    // Expect 1 validation error message: the one from `NotBlank` or `NotNull`.
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertSame('string__not_blank', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals($expected_message, $result->get(0)->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests config validation via the Typed Data API.
 | 
			
		||||
   */
 | 
			
		||||
  public function testSimpleConfigValidation(): void {
 | 
			
		||||
    $config = \Drupal::configFactory()->getEditable('config_test.validation');
 | 
			
		||||
    /** @var \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager */
 | 
			
		||||
    $typed_config_manager = \Drupal::service('config.typed');
 | 
			
		||||
    /** @var \Drupal\Core\Config\Schema\TypedConfigInterface $typed_config */
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertInstanceOf(ConstraintViolationListInterface::class, $result);
 | 
			
		||||
    $this->assertEmpty($result);
 | 
			
		||||
 | 
			
		||||
    // Test constraints on primitive types.
 | 
			
		||||
    $config->set('llama', 'elephant');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    // Its not a valid llama anymore.
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('no valid llama', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constraints on mapping.
 | 
			
		||||
    $config->set('llama', 'llama');
 | 
			
		||||
    $config->set('cat.type', 'nyans');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertEmpty($result);
 | 
			
		||||
 | 
			
		||||
    // Test constrains on nested mapping.
 | 
			
		||||
    $config->set('cat.type', 'tiger');
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('no valid cat', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constrains on sequences elements.
 | 
			
		||||
    $config->set('cat.type', 'nyans');
 | 
			
		||||
    $config->set('giraffe', ['muh', 'hum2']);
 | 
			
		||||
    $config->save();
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('Giraffes just hum', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Test constrains on the sequence itself.
 | 
			
		||||
    $config->set('giraffe', ['hum', 'hum2', 'invalid-key' => 'hum']);
 | 
			
		||||
    $config->save();
 | 
			
		||||
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(1, $result);
 | 
			
		||||
    $this->assertEquals('giraffe', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals('Invalid giraffe key.', $result->get(0)->getMessage());
 | 
			
		||||
 | 
			
		||||
    // Validates mapping.
 | 
			
		||||
    $typed_config = $typed_config_manager->get('config_test.validation');
 | 
			
		||||
    $value = $typed_config->getValue();
 | 
			
		||||
    unset($value['giraffe']);
 | 
			
		||||
    $value['elephant'] = 'foo';
 | 
			
		||||
    $value['zebra'] = 'foo';
 | 
			
		||||
    $typed_config->setValue($value);
 | 
			
		||||
    $result = $typed_config->validate();
 | 
			
		||||
    $this->assertCount(3, $result);
 | 
			
		||||
    // 2 constraint violations triggered by the default validation constraint
 | 
			
		||||
    // for `type: mapping`
 | 
			
		||||
    // @see \Drupal\Core\Validation\Plugin\Validation\Constraint\ValidKeysConstraint
 | 
			
		||||
    $this->assertSame('elephant', $result->get(0)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals("'elephant' is not a supported key.", $result->get(0)->getMessage());
 | 
			
		||||
    $this->assertSame('zebra', $result->get(1)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals("'zebra' is not a supported key.", $result->get(1)->getMessage());
 | 
			
		||||
    // 1 additional constraint violation triggered by the custom
 | 
			
		||||
    // constraint for the `config_test.validation` type, which indirectly
 | 
			
		||||
    // extends `type: mapping` (via `type: config_object`).
 | 
			
		||||
    // @see \Drupal\config_test\ConfigValidation::validateMapping()
 | 
			
		||||
    $this->assertEquals('', $result->get(2)->getPropertyPath());
 | 
			
		||||
    $this->assertEquals('Unexpected keys: elephant, zebra', $result->get(2)->getMessage());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user