class ComponentValidatorTest

Same name and namespace in other branches
  1. 11.x core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php \Drupal\Tests\Core\Theme\Component\ComponentValidatorTest
  2. 10 core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php \Drupal\Tests\Core\Theme\Component\ComponentValidatorTest

Unit tests for the component validation.

Attributes

#[CoversClass(ComponentValidator::class)] #[Group('sdc')]

Hierarchy

Expanded class hierarchy of ComponentValidatorTest

File

core/tests/Drupal/Tests/Core/Theme/Component/ComponentValidatorTest.php, line 27

Namespace

Drupal\Tests\Core\Theme\Component
View source
class ComponentValidatorTest extends UnitTestCase {
  
  /**
   * Tests that valid component definitions don't cause errors.
   *
   * @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
   */
  public function testValidateDefinitionValid(array $definition) : void {
    $component_validator = new ComponentValidator();
    $component_validator->setValidator();
    $this->assertTrue($component_validator->validateDefinition($definition, TRUE), 'The invalid component definition did not throw an error.');
  }
  
  /**
   * Data provider with valid component definitions.
   *
   * @return array
   *   The data.
   */
  public static function dataProviderValidateDefinitionValid() : \Generator {
    $data = array_map([
      static::class,
      'loadComponentDefinitionFromFs',
    ], [
      'my-banner',
      'my-button',
      'my-cta',
    ]);
    foreach ($data as $definition) {
      yield [
        $definition,
      ];
    }
  }
  
  /**
   * Tests invalid component definitions.
   */
  public function testValidateDefinitionInvalid(array $definition) : void {
    $this->expectException(InvalidComponentException::class);
    $component_validator = new ComponentValidator();
    $component_validator->setValidator();
    $component_validator->validateDefinition($definition, TRUE);
  }
  
  /**
   * Data provider with invalid component definitions.
   *
   * @return \Generator
   *   Returns the generator with the invalid definitions.
   */
  public static function dataProviderValidateDefinitionInvalid() : \Generator {
    $valid_cta = static::loadComponentDefinitionFromFs('my-cta');
    $cta_with_missing_required = $valid_cta;
    unset($cta_with_missing_required['path']);
    yield 'missing required' => [
      $cta_with_missing_required,
    ];
    $cta_with_invalid_class = $valid_cta;
    $cta_with_invalid_class['props']['properties']['attributes']['type'] = 'Drupal\\Foo\\Invalid';
    yield 'invalid class' => [
      $cta_with_invalid_class,
    ];
    $cta_with_invalid_enum = array_merge($valid_cta, [
      'extension_type' => 'invalid',
    ]);
    yield 'invalid enum' => [
      $cta_with_invalid_enum,
    ];
    // A list of property types that are not strings, but can be provided via
    // YAML.
    $non_string_types = [
      NULL,
      123,
      123.45,
      TRUE,
    ];
    foreach ($non_string_types as $non_string_type) {
      $cta_with_non_string_prop_type = $valid_cta;
      $cta_with_non_string_prop_type['props']['properties']['text']['type'] = $non_string_type;
      yield "non string type ({$non_string_type})" => [
        $cta_with_non_string_prop_type,
      ];
      // Same, but as a part of the list of allowed types.
      $cta_with_non_string_prop_type['props']['properties']['text']['type'] = [
        'string',
        $non_string_type,
      ];
      yield "non string type ({$non_string_type}) in a list of types" => [
        $cta_with_non_string_prop_type,
      ];
    }
    // The array is a valid value for the 'type' parameter, but it is not
    // allowed as the allowed type.
    $cta_with_non_string_prop_type['props']['properties']['text']['type'] = [
      'string',
      [],
    ];
    yield 'non string type (Array)' => [
      $cta_with_non_string_prop_type,
    ];
    $cta_with_invalid_slot_type = $valid_cta;
    $cta_with_invalid_slot_type['slots'] = [
      'valid_slot' => [
        'title' => 'Valid slot',
        'description' => 'Valid slot description',
      ],
      'invalid_slot' => [
        'title' => [
          'hello' => 'Invalid slot',
          'world' => 'Invalid slot',
        ],
        'description' => 'Title must be string',
      ],
    ];
    yield 'invalid slot (type)' => [
      $cta_with_invalid_slot_type,
    ];
    $cta_with_invalid_slot_name = $valid_cta;
    $cta_with_invalid_slot_name['slots'] = [
      'valid_slot' => [
        'title' => 'Valid slot',
        'description' => 'Valid slot description',
      ],
      'invalid slot' => [
        'title' => 'Invalid slot',
        'description' => 'Slot name cannot have spaces',
      ],
    ];
    yield 'invalid slot (name with spaces)' => [
      $cta_with_invalid_slot_name,
    ];
    $cta_with_invalid_variant_title_type = $valid_cta;
    $cta_with_invalid_variant_title_type['variants'] = [
      'valid_variant' => [
        'title' => 'Valid variant',
        'description' => 'Valid variant description',
      ],
      'invalid_variant' => [
        'title' => [
          'hello' => 'Invalid variant',
          'world' => 'Invalid variant',
        ],
        'description' => 'Title must be string',
      ],
    ];
    yield 'invalid variant title (type)' => [
      $cta_with_invalid_variant_title_type,
    ];
    $cta_with_missing_variant_title_type = $valid_cta;
    $cta_with_missing_variant_title_type['variants'] = [
      'valid_variant' => [
        'title' => 'Valid variant',
        'description' => 'Valid variant description',
      ],
      'invalid_variant' => [
        'description' => 'Title is required',
      ],
    ];
    yield 'invalid variant title (missing title)' => [
      $cta_with_missing_variant_title_type,
    ];
    $cta_with_invalid_variant_description_type = $valid_cta;
    $cta_with_invalid_variant_description_type['variants'] = [
      'valid_variant' => [
        'title' => 'Valid variant',
        'description' => 'Valid variant description',
      ],
      'invalid_variant' => [
        'title' => 'Invalid variant',
        'description' => [
          'this' => 'Description must be',
          'that' => 'a string',
        ],
      ],
    ];
    yield 'invalid variant description (type)' => [
      $cta_with_invalid_variant_description_type,
    ];
  }
  
  /**
   * Tests that valid props are handled properly.
   *
   * @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
   */
  public function testValidatePropsValid(array $context, string $component_id, array $definition) : void {
    $translation = $this->getStringTranslationStub();
    $container = new ContainerBuilder();
    $container->set('string_translation', $translation);
    \Drupal::setContainer($container);
    $component = new Component([
      'app_root' => '/fake/path/root',
    ], 'sdc_test:' . $component_id, $definition);
    $component_validator = new ComponentValidator();
    $component_validator->setValidator();
    $this->assertTrue($component_validator->validateProps($context, $component), 'The valid component props threw an error.');
  }
  
  /**
   * Data provider with valid component props.
   *
   * @return array
   *   The data.
   */
  public static function dataProviderValidatePropsValid() : array {
    return [
      [
        [
          'text' => 'Can Pica',
          'href' => 'https://www.drupal.org',
          'target' => '_blank',
          'attributes' => new Attribute([
            'key' => 'value',
          ]),
        ],
        'my-cta',
        static::loadComponentDefinitionFromFs('my-cta'),
      ],
      [
        [],
        'my-banner',
        static::loadComponentDefinitionFromFs('my-banner'),
      ],
      [
        [
          'nonProp' => new \stdClass(),
        ],
        'my-banner',
        static::loadComponentDefinitionFromFs('my-banner'),
      ],
    ];
  }
  
  /**
   * Tests we can use a custom validator to validate props.
   */
  public function testCustomValidator() : void {
    $translation = $this->getStringTranslationStub();
    $container = new ContainerBuilder();
    $container->set('string_translation', $translation);
    \Drupal::setContainer($container);
    $component = new Component([
      'app_root' => '/fake/path/root',
    ], 'sdc_test:my-cta', static::loadComponentDefinitionFromFs('my-cta'));
    $component_validator = new ComponentValidator();
    // A validator with a constraint factory that uses a custom constraint for
    // checking format.
    $component_validator->setValidator(new Validator((new Factory())->setConstraintClass('format', UrlHelperFormatConstraint::class)));
    self::assertTrue($component_validator->validateProps([
      'text' => 'Can Pica',
      // This is a valid URI but for v5.2 of justinrainbow/json-schema it
      // does not pass validation without a custom constraint for format.
      // We pass a custom factory and it should be used.
'href' => 'entity:node/1',
      'target' => '_blank',
      'attributes' => new Attribute([
        'key' => 'value',
      ]),
    ], $component), 'The valid component props threw an error.');
  }
  
  /**
   * Tests that invalid props are handled properly.
   *
   * @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
   */
  public function testValidatePropsInvalid(array $context, string $component_id, array $definition, string $expected_exception_message) : void {
    $translation = $this->getStringTranslationStub();
    $container = new ContainerBuilder();
    $container->set('string_translation', $translation);
    \Drupal::setContainer($container);
    $component = new Component([
      'app_root' => '/fake/path/root',
    ], 'sdc_test:' . $component_id, $definition);
    $this->expectException(InvalidComponentException::class);
    $this->expectExceptionMessage($expected_exception_message);
    $component_validator = new ComponentValidator();
    $component_validator->setValidator();
    $component_validator->validateProps($context, $component);
  }
  
  /**
   * Data provider with invalid component props.
   *
   * @return array
   *   Returns the generator with the invalid properties.
   */
  public static function dataProviderValidatePropsInvalid() : array {
    return [
      'missing required prop' => [
        [
          'href' => 'https://www.drupal.org',
          'target' => '_blank',
          'attributes' => new Attribute([
            'key' => 'value',
          ]),
        ],
        'my-cta',
        static::loadComponentDefinitionFromFs('my-cta'),
        '[sdc_test:my-cta/text] The property text is required.',
      ],
      'attributes with invalid object class' => [
        [
          'text' => 'Can Pica',
          'href' => 'https://www.drupal.org',
          'target' => '_blank',
          'attributes' => new \stdClass(),
        ],
        'my-cta',
        static::loadComponentDefinitionFromFs('my-cta'),
        'Data provided to prop "attributes" for component "sdc_test:my-cta" is not a valid instance of "Drupal\\Core\\Template\\Attribute"',
      ],
      'ctaTarget violates the allowed properties in the enum' => [
        [
          'ctaTarget' => 'foo',
        ],
        'my-banner',
        static::loadComponentDefinitionFromFs('my-banner'),
        '[sdc_test:my-banner/ctaTarget] Does not have a value in the enumeration ["","_blank"]. The provided value is: "foo".',
      ],
    ];
  }
  
  /**
   * Loads a component definition from the component name.
   *
   * @param string $component_name
   *   The component name.
   *
   * @return array
   *   The component definition
   */
  private static function loadComponentDefinitionFromFs(string $component_name) : array {
    return array_merge(Yaml::parseFile(sprintf('%s/modules/system/tests/modules/sdc_test/components/%s/%s.component.yml', dirname(__DIR__, 6), $component_name, $component_name)), [
      'machineName' => $component_name,
      'extension_type' => 'module',
      'id' => 'sdc_test:' . $component_name,
      'library' => [
        'css' => [
          'component' => [
            'foo.css' => [],
          ],
        ],
      ],
      'path' => '',
      'provider' => 'sdc_test',
      'template' => $component_name . '.twig',
      'group' => 'my-group',
      'description' => 'My description',
    ]);
  }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overrides
ComponentValidatorTest::dataProviderValidateDefinitionInvalid public static function Data provider with invalid component definitions.
ComponentValidatorTest::dataProviderValidateDefinitionValid public static function Data provider with valid component definitions.
ComponentValidatorTest::dataProviderValidatePropsInvalid public static function Data provider with invalid component props.
ComponentValidatorTest::dataProviderValidatePropsValid public static function Data provider with valid component props.
ComponentValidatorTest::loadComponentDefinitionFromFs private static function Loads a component definition from the component name.
ComponentValidatorTest::testCustomValidator public function Tests we can use a custom validator to validate props.
ComponentValidatorTest::testValidateDefinitionInvalid public function Tests invalid component definitions.
ComponentValidatorTest::testValidateDefinitionValid public function Tests that valid component definitions don't cause errors.
ComponentValidatorTest::testValidatePropsInvalid public function Tests that invalid props are handled properly.
ComponentValidatorTest::testValidatePropsValid public function Tests that valid props are handled properly.
DrupalTestCaseTrait::checkErrorHandlerOnTearDown public function Checks the test error handler after test execution.
ExpectDeprecationTrait::expectDeprecation Deprecated public function Adds an expected deprecation.
ExpectDeprecationTrait::regularExpressionForFormatDescription private function
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.
UnitTestCase::$root protected property The app root.
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::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::setDebugDumpHandler public static function Registers the dumper CLI handler when the DebugDump extension is enabled.
UnitTestCase::setUp protected function 366
UnitTestCase::setupMockIterator protected function Set up a traversable class mock to return specific items when iterated.

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