WildcardConfigActionsTest.php

Same filename and directory in other branches
  1. 11.x core/tests/Drupal/KernelTests/Core/Recipe/WildcardConfigActionsTest.php
  2. 10 core/tests/Drupal/KernelTests/Core/Recipe/WildcardConfigActionsTest.php

Namespace

Drupal\KernelTests\Core\Recipe

File

core/tests/Drupal/KernelTests/Core/Recipe/WildcardConfigActionsTest.php

View source
<?php

declare (strict_types=1);
namespace Drupal\KernelTests\Core\Recipe;

use Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Config\Action\ConfigActionException;
use Drupal\Core\Config\Action\Plugin\ConfigAction\CreateForEachBundle;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Recipe\InvalidConfigException;
use Drupal\Core\Recipe\RecipeRunner;
use Drupal\entity_test\Entity\EntityTestBundle;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\FunctionalTests\Core\Recipe\RecipeTestTrait;
use Drupal\image\Entity\ImageStyle;
use Drupal\KernelTests\KernelTestBase;
use Drupal\language\Entity\ContentLanguageSettings;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
use PHPUnit\Framework\Attributes\TestWith;
use Symfony\Component\Validator\Constraints\NotNull;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;

/**
 * Tests config actions targeting multiple entities using wildcards.
 */
class WildcardConfigActionsTest extends KernelTestBase {
  use ContentTypeCreationTrait;
  use RecipeTestTrait;
  
  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'entity_test',
    'field',
    'node',
    'system',
    'text',
    'user',
  ];
  
  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $this->installEntitySchema('node');
    $this->installConfig('node');
    $this->createContentType([
      'type' => 'one',
      'name' => 'Type A',
    ]);
    $this->createContentType([
      'type' => 'two',
      'name' => 'Type B',
    ]);
    $this->installEntitySchema('entity_test_with_bundle');
    EntityTestBundle::create([
      'id' => 'one',
    ])->save();
    EntityTestBundle::create([
      'id' => 'two',
    ])->save();
    $field_storage = FieldStorageConfig::create([
      'entity_type' => 'entity_test_with_bundle',
      'field_name' => 'field_test',
      'type' => 'boolean',
    ]);
    $field_storage->save();
    FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'one',
    ])->save();
    FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'two',
    ])->save();
    $field_storage = FieldStorageConfig::create([
      'entity_type' => 'node',
      'field_name' => 'field_test',
      'type' => 'boolean',
    ]);
    $field_storage->save();
    FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'one',
    ])->save();
    FieldConfig::create([
      'field_storage' => $field_storage,
      'bundle' => 'two',
    ])->save();
  }
  
  /**
   * Tests targeting multiple config entities for an action, using wildcards.
   *
   * @param string $expression
   *   The expression the recipe will use to target multiple config entities.
   * @param string[] $expected_changed_entities
   *   The IDs of the config entities that we expect the recipe to change.
   */
  // phpcs:disable Drupal.Arrays.Array.LongLineDeclaration
  public function testTargetEntitiesByWildcards(string $expression, array $expected_changed_entities) : void {
    $contents = <<<YAML
    name: 'Wildcards!'
    config:
      actions:
        {<span class="php-variable">$expression</span>}:
          setLabel: 'Changed by config action'
    YAML;
    $recipe = $this->createRecipe($contents);
    RecipeRunner::processRecipe($recipe);
    $changed = $this->container
      ->get(EntityTypeManagerInterface::class)
      ->getStorage('field_config')
      ->getQuery()
      ->condition('label', 'Changed by config action')
      ->execute();
    sort($expected_changed_entities);
    sort($changed);
    $this->assertSame($expected_changed_entities, array_values($changed));
  }
  
  /**
   * Tests that an invalid wildcard expression will raise an error.
   */
  // phpcs:disable Drupal.Arrays.Array.LongLineDeclaration
  public function testInvalidExpression(string $expression, string $expected_exception_message) : void {
    $contents = <<<YAML
    name: 'Wildcards gone wild...'
    config:
      actions:
        {<span class="php-variable">$expression</span>}:
          simpleConfigUpdate:
            label: 'Changed by config action'
    YAML;
    $recipe = $this->createRecipe($contents);
    $this->expectException(ConfigActionException::class);
    $this->expectExceptionMessage($expected_exception_message);
    RecipeRunner::processRecipe($recipe);
  }
  
  /**
   * Tests that the createForEach action works as expected in normal conditions.
   */
  public function testCreateForEach() : void {
    $this->enableModules([
      'image',
      'language',
    ]);
    /** @var \Drupal\Core\Config\Action\ConfigActionManager $manager */
    $manager = $this->container
      ->get('plugin.manager.config_action');
    $manager->applyAction('createForEach', 'node.type.*', [
      'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
      ],
    ]);
    $this->assertIsObject(ContentLanguageSettings::load('node.one'));
    $this->assertIsObject(ContentLanguageSettings::load('node.two'));
  }
  
  /**
   * Tests that the createForEach action validates the config it creates.
   */
  public function testCreateForEachValidatesCreatedEntities() : void {
    $this->enableModules([
      'image',
    ]);
    // To prove that the validation runs, we need to disable strict schema
    // checking in this test. We need to explicitly unsubscribe it from events
    // because by this point in the test it has been fully wired up into the
    // container and can't be changed.
    $schema_checker = $this->container
      ->get('testing.config_schema_checker');
    $this->container
      ->get(EventDispatcherInterface::class)
      ->removeSubscriber($schema_checker);
    try {
      $this->container
        ->get('plugin.manager.config_action')
        ->applyAction('createForEach', 'node.type.*', [
        'image.style.node__%bundle' => [],
      ]);
      $this->fail('Expected an exception to be thrown but it was not.');
    } catch (InvalidConfigException $e) {
      $this->assertSame('image.style.node__one', $e->data
        ->getName());
      $this->assertCount(1, $e->violations);
      $this->assertSame('label', $e->violations[0]
        ->getPropertyPath());
      $this->assertSame(NotNull::IS_NULL_ERROR, $e->violations[0]
        ->getCode());
    }
  }
  
  /**
   * Tests using the `%label` placeholder with the createForEach action.
   */
  public function testCreateForEachWithLabel() : void {
    $this->enableModules([
      'image',
    ]);
    // We should be able to use the `%label` placeholder.
    // Also ensure nested and non-string keys/values are handled correctly.
    $this->container
      ->get('plugin.manager.config_action')
      ->applyAction('createForEach', 'node.type.*', [
      'image.style.node_%bundle_big' => [
        'label' => 'Big image for %label content',
        'effects' => [
          [
            'id' => 'image_scale',
            'weight' => 10,
          ],
        ],
      ],
    ]);
    $this->assertSame('Big image for Type A content', ImageStyle::load('node_one_big')?->label());
    $this->assertSame('Big image for Type B content', ImageStyle::load('node_two_big')?->label());
  }
  
  /**
   * Tests that the createForEachIfNotExists action ignores existing config.
   */
  public function testCreateForEachIfNotExists() : void {
    $this->enableModules([
      'language',
    ]);
    ContentLanguageSettings::create([
      'target_entity_type_id' => 'node',
      'target_bundle' => 'one',
    ])->save();
    $this->container
      ->get('plugin.manager.config_action')
      ->applyAction('createForEachIfNotExists', 'node.type.*', [
      'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
      ],
    ]);
    $this->assertIsObject(ContentLanguageSettings::loadByEntityTypeBundle('node', 'two'));
  }
  
  /**
   * Tests that the createForEach action errs on conflict with existing config.
   */
  public function testCreateForEachErrorsIfAlreadyExists() : void {
    $this->enableModules([
      'language',
    ]);
    ContentLanguageSettings::create([
      'target_entity_type_id' => 'node',
      'target_bundle' => 'one',
    ])->save();
    $this->expectExceptionMessage(ConfigActionException::class);
    $this->expectExceptionMessage('Entity language.content_settings.node.one exists');
    $this->container
      ->get('plugin.manager.config_action')
      ->applyAction('createForEach', 'node.type.*', [
      'language.content_settings.node.%bundle' => [
        'target_entity_type_id' => 'node',
        'target_bundle' => '%bundle',
      ],
    ]);
  }
  
  /**
   * Tests that the createForEach action only works on bundle entities.
   */
  public function testCreateForEachNotAvailableOnNonBundleEntities() : void {
    $this->enableModules([
      'language',
    ]);
    // We should not be able to use this action on entities that aren't
    // themselves bundles of another entity type.
    $this->expectException(PluginNotFoundException::class);
    $this->expectExceptionMessage('The "language_content_settings" entity does not support the "createForEach" config action.');
    $this->container
      ->get('plugin.manager.config_action')
      ->applyAction('createForEach', 'language.content_settings.node.*', []);
  }
  
  /**
   * Tests that the createForEach action requires bundle entity types to exist.
   */
  public function testCreateForEachErrorsIfNoBundleEntityTypesExist() : void {
    $this->disableModules([
      'node',
      'entity_test',
    ]);
    $manager = $this->container
      ->get('plugin.manager.config_action');
    $manager->clearCachedDefinitions();
    $this->expectException(InvalidPluginDefinitionException::class);
    $this->expectExceptionMessage('The create_for_each_bundle:createForEach config action must be restricted to entity types that are bundles of another entity type.');
    $manager->applyAction('create_for_each_bundle:createForEach', 'node.type.*', []);
  }

}

Classes

Title Deprecated Summary
WildcardConfigActionsTest Tests config actions targeting multiple entities using wildcards.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.