class ConfigTargetTest

Same name in this branch
  1. 10 core/modules/system/tests/src/FunctionalJavascript/Form/ConfigTargetTest.php \Drupal\Tests\system\FunctionalJavascript\Form\ConfigTargetTest
  2. 10 core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php \Drupal\Tests\system\Functional\Form\ConfigTargetTest
Same name and namespace in other branches
  1. 11.x core/modules/system/tests/src/FunctionalJavascript/Form/ConfigTargetTest.php \Drupal\Tests\system\FunctionalJavascript\Form\ConfigTargetTest
  2. 11.x core/modules/system/tests/src/Functional/Form/ConfigTargetTest.php \Drupal\Tests\system\Functional\Form\ConfigTargetTest
  3. 11.x core/tests/Drupal/Tests/Core/Form/ConfigTargetTest.php \Drupal\Tests\Core\Form\ConfigTargetTest

@coversDefaultClass \Drupal\Core\Form\ConfigTarget
@group Form

Hierarchy

Expanded class hierarchy of ConfigTargetTest

File

core/tests/Drupal/Tests/Core/Form/ConfigTargetTest.php, line 23

Namespace

Drupal\Tests\Core\Form
View source
class ConfigTargetTest extends UnitTestCase {
  
  /**
   * @covers \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap
   */
  public function testDuplicateTargetsNotAllowed() : void {
    $form = [
      'test' => [
        '#type' => 'text',
        '#default_value' => 'A test',
        '#config_target' => new ConfigTarget('system.site', 'admin_compact_mode', 'intval', 'boolval'),
        '#name' => 'test',
        '#array_parents' => [
          'test',
        ],
      ],
      'duplicate' => [
        '#type' => 'text',
        '#config_target' => new ConfigTarget('system.site', 'admin_compact_mode', 'intval', 'boolval'),
        '#name' => 'duplicate',
        '#array_parents' => [
          'duplicate',
        ],
      ],
    ];
    $test_form = new class ($this->prophesize(ConfigFactoryInterface::class)
      ->reveal(), $this->prophesize(TypedConfigManagerInterface::class)
      ->reveal()) extends ConfigFormBase {
      use RedundantEditableConfigNamesTrait;
      public function getFormId() {
        return 'test';
      }

};
    $form_state = new FormState();
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage('Two #config_targets both target "admin_compact_mode" in the "system.site" config: `$form[\'test\']` and `$form[\'duplicate\']`.');
    $test_form->storeConfigKeyToFormElementMap($form, $form_state);
  }
  
  /**
   * @covers \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap
   * @dataProvider providerTestFormCacheable
   */
  public function testFormCacheable(bool $expected, ?callable $fromConfig, ?callable $toConfig) : void {
    $form = [
      'test' => [
        '#type' => 'text',
        '#default_value' => 'A test',
        '#config_target' => new ConfigTarget('system.site', 'admin_compact_mode', $fromConfig, $toConfig),
        '#name' => 'test',
        '#array_parents' => [
          'test',
        ],
      ],
    ];
    $test_form = new class ($this->prophesize(ConfigFactoryInterface::class)
      ->reveal(), $this->prophesize(TypedConfigManagerInterface::class)
      ->reveal()) extends ConfigFormBase {
      use RedundantEditableConfigNamesTrait;
      public function getFormId() {
        return 'test';
      }

};
    $form_state = new FormState();
    // Make the form cacheable.
    $form_state->setRequestMethod('POST')
      ->setCached();
    $test_form->storeConfigKeyToFormElementMap($form, $form_state);
    $this->assertSame($expected, $form_state->isCached());
  }
  public static function providerTestFormCacheable() : array {
    $closure = fn(bool $something): string => $something ? 'Yes' : 'No';
    return [
      'No callables' => [
        TRUE,
        NULL,
        NULL,
      ],
      'Serializable fromConfig callable' => [
        TRUE,
        "intval",
        NULL,
      ],
      'Serializable toConfig callable' => [
        TRUE,
        NULL,
        "boolval",
      ],
      'Serializable callables' => [
        TRUE,
        "intval",
        "boolval",
      ],
      'Unserializable fromConfig callable' => [
        FALSE,
        $closure,
        NULL,
      ],
      'Unserializable toConfig callable' => [
        FALSE,
        NULL,
        $closure,
      ],
      'Unserializable callables' => [
        FALSE,
        $closure,
        $closure,
      ],
    ];
  }
  
  /**
   * @covers ::fromForm
   * @covers ::fromString
   */
  public function testFromFormString() : void {
    $form = [
      'group' => [
        '#type' => 'details',
        'test' => [
          '#type' => 'text',
          '#default_value' => 'A test',
          '#config_target' => 'system.site:name',
          '#name' => 'test',
          '#parents' => [
            'test',
          ],
        ],
      ],
    ];
    $config_target = ConfigTarget::fromForm([
      'group',
      'test',
    ], $form);
    $this->assertSame('system.site', $config_target->configName);
    $this->assertSame([
      'name',
    ], $config_target->propertyPaths);
    $this->assertSame([
      'test',
    ], $config_target->elementParents);
  }
  
  /**
   * @covers ::fromForm
   */
  public function testFromFormConfigTarget() : void {
    $form = [
      'test' => [
        '#type' => 'text',
        '#default_value' => 'A test',
        '#config_target' => new ConfigTarget('system.site', 'admin_compact_mode', 'intval', 'boolval'),
        '#name' => 'test',
        '#parents' => [
          'test',
        ],
      ],
    ];
    $config_target = ConfigTarget::fromForm([
      'test',
    ], $form);
    $this->assertSame('system.site', $config_target->configName);
    $this->assertSame([
      'admin_compact_mode',
    ], $config_target->propertyPaths);
    $this->assertSame([
      'test',
    ], $config_target->elementParents);
    $this->assertSame(1, ($config_target->fromConfig)(TRUE));
    $this->assertFalse(($config_target->toConfig)('0'));
  }
  
  /**
   * @covers ::fromForm
   * @dataProvider providerTestFromFormException
   */
  public function testFromFormException(array $form, array $array_parents, string $exception_message) : void {
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage($exception_message);
    ConfigTarget::fromForm($array_parents, $form);
  }
  public static function providerTestFromFormException() : array {
    return [
      'No #config_target' => [
        [
          'test' => [
            '#type' => 'text',
            '#default_value' => 'A test',
          ],
        ],
        [
          'test',
        ],
        'The form element [test] does not have the #config_target property set',
      ],
      'No #config_target nested' => [
        [
          'group' => [
            '#type' => 'details',
            'test' => [
              '#type' => 'text',
              '#default_value' => 'A test',
            ],
          ],
        ],
        [
          'group',
          'test',
        ],
        'The form element [group][test] does not have the #config_target property set',
      ],
      'Boolean #config_target nested' => [
        [
          'group' => [
            '#type' => 'details',
            'test' => [
              '#type' => 'text',
              '#config_target' => FALSE,
              '#default_value' => 'A test',
            ],
          ],
        ],
        [
          'group',
          'test',
        ],
        'The form element [group][test] #config_target property is not a string or a ConfigTarget object',
      ],
    ];
  }
  
  /**
   * @dataProvider providerMultiTargetWithoutCallables
   */
  public function testMultiTargetWithoutCallables(...$arguments) : void {
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage('The $fromConfig and $toConfig arguments must be passed to Drupal\\Core\\Form\\ConfigTarget::__construct() if multiple property paths are targeted.');
    new ConfigTarget(...$arguments);
  }
  public static function providerMultiTargetWithoutCallables() : \Generator {
    (yield "neither callable" => [
      'foo.settings',
      [
        'a',
        'b',
      ],
    ]);
    (yield "only fromConfig" => [
      'foo.settings',
      [
        'a',
        'b',
      ],
      "intval",
    ]);
    (yield "only toConfig" => [
      'foo.settings',
      [
        'a',
        'b',
      ],
      NULL,
      "intval",
    ]);
  }
  public function testGetValueCorrectConfig() : void {
    $sut = new ConfigTarget('foo.settings', $this->randomMachineName());
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('bar.settings');
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage('Config target is associated with foo.settings but bar.settings given.');
    $sut->getValue($config->reveal());
  }
  public function testSetValueCorrectConfig() : void {
    $sut = new ConfigTarget('foo.settings', $this->randomMachineName());
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('bar.settings');
    $this->expectException(\InvalidArgumentException::class);
    $this->expectExceptionMessage('Config target is associated with foo.settings but bar.settings given.');
    $sut->setValue($config->reveal(), $this->randomString(), $this->prophesize(FormStateInterface::class)
      ->reveal());
  }
  public function testSingleTarget() : void {
    $config_target = new ConfigTarget('foo.settings', 'something', fromConfig: fn(bool $something): string => $something ? 'Yes' : 'No', toConfig: fn(string $form_value): ToConfig|bool => match ($form_value) {  'Yes' => TRUE,
      '<test:noop>' => ToConfig::NoOp,
      '<test:delete>' => ToConfig::DeleteKey,
      default => FALSE,
    
    });
    // Assert the logic in the callables works as expected.
    $this->assertSame("Yes", ($config_target->fromConfig)(TRUE));
    $this->assertSame("No", ($config_target->fromConfig)(FALSE));
    $this->assertTrue(($config_target->toConfig)("Yes"));
    $this->assertFalse(($config_target->toConfig)("No"));
    $this->assertFalse(($config_target->toConfig)("some random string"));
    $this->assertSame(ToConfig::NoOp, ($config_target->toConfig)("<test:noop>"));
    $this->assertSame(ToConfig::DeleteKey, ($config_target->toConfig)("<test:delete>"));
    // Now simulate how this will be used in the form, and ensure it results in
    // the expected Config::set() calls.
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('foo.settings');
    // First to transform the stored config value to the form value.
    $config->get('something')
      ->willReturn(TRUE);
    $this->assertSame("Yes", $config_target->getValue($config->reveal()));
    // Then to transform the modified form value back to config.
    $config->set('something', TRUE)
      ->shouldBeCalledTimes(1);
    $config_target->setValue($config->reveal(), 'Yes', $this->prophesize(FormStateInterface::class)
      ->reveal());
    // Repeat, but for the other possible value.
    $config->get('something')
      ->willReturn(FALSE);
    $this->assertSame("No", $config_target->getValue($config->reveal()));
    $config->set('something', FALSE)
      ->shouldBeCalledTimes(1);
    $config_target->setValue($config->reveal(), 'No', $this->prophesize(FormStateInterface::class)
      ->reveal());
    // Test `ToConfig::NoOp`: nothing should happen to the Config.
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('foo.settings');
    $config->set('something', Argument::any())
      ->shouldBeCalledTimes(0);
    $config->clear('something', Argument::any())
      ->shouldBeCalledTimes(0);
    $config_target->setValue($config->reveal(), '<test:noop>', $this->prophesize(FormStateInterface::class)
      ->reveal());
    // Test `ToConfig::DeleteKey`: Config::clear() should be called.
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('foo.settings');
    $config->clear('something')
      ->shouldBeCalledTimes(1);
    $config_target->setValue($config->reveal(), '<test:delete>', $this->prophesize(FormStateInterface::class)
      ->reveal());
  }
  public function testMultiTarget() : void {
    $config_target = new ConfigTarget('foo.settings', [
      'first',
      'second',
    ], fromConfig: fn(int $first, int $second): string => "{$first}|{$second}", toConfig: fn(string $form_value): array => [
      'first' => intval(explode('|', $form_value)[0]),
      'second' => intval(explode('|', $form_value)[1]),
    ]);
    // Assert the logic in the callables works as expected.
    $this->assertSame("42|-4", ($config_target->fromConfig)(42, -4));
    $this->assertSame([
      'first' => 9,
      'second' => 19,
    ], ($config_target->toConfig)("9|19"));
    // Now simulate how this will be used in the form, and ensure it results in
    // the expected Config::set() calls.
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('foo.settings');
    // First to transform the stored config value to the form value.
    $config->get('first')
      ->willReturn(-17);
    $config->get('second')
      ->willReturn(71);
    $this->assertSame("-17|71", $config_target->getValue($config->reveal()));
    // Then to transform the modified form value back to config.
    $config->set('first', 1988)
      ->shouldBeCalledTimes(1);
    $config->set('second', 1992)
      ->shouldBeCalledTimes(1);
    $config_target->setValue($config->reveal(), '1988|1992', $this->prophesize(FormStateInterface::class)
      ->reveal());
  }
  
  /**
   * @testWith ["this string was returned by toConfig", "The toConfig callable returned a string, but it must be an array with a key-value pair for each of the targeted property paths."]
   *           [true, "The toConfig callable returned a boolean, but it must be an array with a key-value pair for each of the targeted property paths."]
   *           [42, "The toConfig callable returned a integer, but it must be an array with a key-value pair for each of the targeted property paths."]
   *           [[], "The toConfig callable returned an array that is missing key-value pairs for the following targeted property paths: first, second."]
   *           [{"yar": 42}, "The toConfig callable returned an array that is missing key-value pairs for the following targeted property paths: first, second."]
   *           [{"FIRST": 42, "SECOND": 1337}, "The toConfig callable returned an array that is missing key-value pairs for the following targeted property paths: first, second."]
   *           [{"second": 42}, "The toConfig callable returned an array that is missing key-value pairs for the following targeted property paths: first."]
   *           [{"first": 42}, "The toConfig callable returned an array that is missing key-value pairs for the following targeted property paths: second."]
   *           [{"first": 42, "second": 1337, "yar": "har"}, "The toConfig callable returned an array that contains key-value pairs that do not match targeted property paths: yar."]
   */
  public function testSetValueMultiTargetToConfigReturnValue(mixed $toConfigReturnValue, string $expected_exception_message) : void {
    $config_target = new ConfigTarget('foo.settings', [
      'first',
      'second',
    ], fromConfig: fn(int $first, int $second): string => "{$first}|{$second}", toConfig: fn(): mixed => $toConfigReturnValue);
    $config = $this->prophesize(Config::class);
    $config->getName()
      ->willReturn('foo.settings');
    $this->expectException(\LogicException::class);
    $this->expectExceptionMessage($expected_exception_message);
    $config_target->setValue($config->reveal(), '1988|1992', $this->prophesize(FormStateInterface::class)
      ->reveal());
  }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overrides
ConfigTargetTest::providerMultiTargetWithoutCallables public static function
ConfigTargetTest::providerTestFormCacheable public static function
ConfigTargetTest::providerTestFromFormException public static function
ConfigTargetTest::testDuplicateTargetsNotAllowed public function @covers \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap[[api-linebreak]]
ConfigTargetTest::testFormCacheable public function @covers \Drupal\Core\Form\ConfigFormBase::storeConfigKeyToFormElementMap[[api-linebreak]]
@dataProvider providerTestFormCacheable
ConfigTargetTest::testFromFormConfigTarget public function @covers ::fromForm[[api-linebreak]]
ConfigTargetTest::testFromFormException public function @covers ::fromForm[[api-linebreak]]
@dataProvider providerTestFromFormException
ConfigTargetTest::testFromFormString public function @covers ::fromForm[[api-linebreak]]
@covers ::fromString[[api-linebreak]]
ConfigTargetTest::testGetValueCorrectConfig public function
ConfigTargetTest::testMultiTarget public function
ConfigTargetTest::testMultiTargetWithoutCallables public function @dataProvider providerMultiTargetWithoutCallables
ConfigTargetTest::testSetValueCorrectConfig public function
ConfigTargetTest::testSetValueMultiTargetToConfigReturnValue public function @testWith [&quot;this string was returned by toConfig&quot;, &quot;The toConfig callable returned a string, but it must be an array with a key-value pair for each of the targeted property paths.&quot;][[api-linebreak]]
[true, &quot;The toConfig…
ConfigTargetTest::testSingleTarget public function
PhpUnitWarnings::$deprecationWarnings private static property Deprecation warnings from PHPUnit to raise with @trigger_error().
PhpUnitWarnings::addWarning public function Converts PHPUnit deprecation warnings to E_USER_DEPRECATED.
RandomGeneratorTrait::getRandomGenerator protected function Gets the random generator for the utility methods.
RandomGeneratorTrait::randomMachineName protected function Generates a unique random string containing letters and numbers.
RandomGeneratorTrait::randomObject public function Generates a random PHP object.
RandomGeneratorTrait::randomString public function Generates a pseudo-random string of ASCII characters of codes 32 to 126.
RandomGeneratorTrait::randomStringValidate Deprecated public function Callback for random string validation.
UnitTestCase::$root protected property The app root. 1
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getConfigStorageStub public function Returns a stub config storage that returns the supplied configuration.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::setUp protected function 358
UnitTestCase::setUpBeforeClass public static function
UnitTestCase::__get public function

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