Initial Drupal 11 with DDEV setup
This commit is contained in:
		@ -0,0 +1,155 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Form\FormInterface;
 | 
			
		||||
use Drupal\Core\Form\FormState;
 | 
			
		||||
use Drupal\Core\Form\FormStateInterface;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the correct rendering of components in form.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentInFormTest extends ComponentKernelTestBase implements FormInterface {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'sdc_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function getFormId(): string {
 | 
			
		||||
    return 'component_in_form_test';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state): array {
 | 
			
		||||
    $form['normal'] = [
 | 
			
		||||
      '#type' => 'textfield',
 | 
			
		||||
      '#title' => 'Normal form element',
 | 
			
		||||
      '#default_value' => 'fake 1',
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    // We want to test form elements inside a component, itself inside a
 | 
			
		||||
    // component.
 | 
			
		||||
    $form['banner'] = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'ctaText' => 'Click me!',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      'banner_body' => [
 | 
			
		||||
        '#type' => 'component',
 | 
			
		||||
        '#component' => 'sdc_theme_test:my-card',
 | 
			
		||||
        '#props' => [
 | 
			
		||||
          'header' => 'Card header',
 | 
			
		||||
        ],
 | 
			
		||||
        'card_body' => [
 | 
			
		||||
          'foo' => [
 | 
			
		||||
            '#type' => 'textfield',
 | 
			
		||||
            '#title' => 'Textfield in component',
 | 
			
		||||
            '#default_value' => 'fake 2',
 | 
			
		||||
          ],
 | 
			
		||||
          'bar' => [
 | 
			
		||||
            '#type' => 'select',
 | 
			
		||||
            '#title' => 'Select in component',
 | 
			
		||||
            '#options' => [
 | 
			
		||||
              'option_1' => 'Option 1',
 | 
			
		||||
              'option_2' => 'Option 2',
 | 
			
		||||
            ],
 | 
			
		||||
            '#empty_option' => 'Empty option',
 | 
			
		||||
            '#default_value' => 'option_1',
 | 
			
		||||
          ],
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = [
 | 
			
		||||
      '#type' => 'actions',
 | 
			
		||||
      'submit' => [
 | 
			
		||||
        '#type' => 'submit',
 | 
			
		||||
        '#value' => 'Submit',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function validateForm(array &$form, FormStateInterface $form_state): void {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function submitForm(array &$form, FormStateInterface $form_state): void {
 | 
			
		||||
    // Check that submitted data are present (set with #default_value).
 | 
			
		||||
    $data = [
 | 
			
		||||
      'normal' => 'fake 1',
 | 
			
		||||
      'foo' => 'fake 2',
 | 
			
		||||
      'bar' => 'option_1',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($data as $key => $value) {
 | 
			
		||||
      $this->assertSame($value, $form_state->getValue($key));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests that fields validation messages are sorted in the fields order.
 | 
			
		||||
   */
 | 
			
		||||
  public function testFormRenderingAndSubmission(): void {
 | 
			
		||||
    /** @var \Drupal\Core\Form\FormBuilderInterface $form_builder */
 | 
			
		||||
    $form_builder = \Drupal::service('form_builder');
 | 
			
		||||
    /** @var \Drupal\Core\Render\RendererInterface $renderer */
 | 
			
		||||
    $renderer = \Drupal::service('renderer');
 | 
			
		||||
    $form = $form_builder->getForm($this);
 | 
			
		||||
 | 
			
		||||
    // Test form structure after being processed.
 | 
			
		||||
    $this->assertTrue($form['normal']['#processed'], 'The normal textfield should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['banner']['banner_body']['card_body']['bar']['#processed'], 'The textfield inside component should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['banner']['banner_body']['card_body']['foo']['#processed'], 'The select inside component should have been processed.');
 | 
			
		||||
    $this->assertTrue($form['actions']['submit']['#processed'], 'The submit button should have been processed.');
 | 
			
		||||
 | 
			
		||||
    // Test form rendering.
 | 
			
		||||
    $markup = $renderer->renderRoot($form);
 | 
			
		||||
    $this->setRawContent($markup);
 | 
			
		||||
 | 
			
		||||
    // Ensure form elements are rendered once.
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('input[name="normal"]'), 'The normal textfield should have been rendered once.');
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('input[name="foo"]'), 'The foo textfield should have been rendered once.');
 | 
			
		||||
    $this->assertCount(1, $this->cssSelect('select[name="bar"]'), 'The bar select should have been rendered once.');
 | 
			
		||||
 | 
			
		||||
    // Check the position of the form elements in the DOM.
 | 
			
		||||
    $paths = [
 | 
			
		||||
      '//form/div[1]/input[@name="normal"]',
 | 
			
		||||
      '//form/div[2][@data-component-id="sdc_test:my-banner"]/div[2][@class="component--my-banner--body"]/div[1][@data-component-id="sdc_theme_test:my-card"]/div[1][@class="component--my-card__body"]/div[1]/input[@name="foo"]',
 | 
			
		||||
      '//form/div[2][@data-component-id="sdc_test:my-banner"]/div[2][@class="component--my-banner--body"]/div[1][@data-component-id="sdc_theme_test:my-card"]/div[1][@class="component--my-card__body"]/div[2]/select[@name="bar"]',
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($paths as $path) {
 | 
			
		||||
      $this->assertNotEmpty($this->xpath($path), 'There should be a result with the path: ' . $path . '.');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Test form submission. Assertions are in submitForm().
 | 
			
		||||
    $form_state = new FormState();
 | 
			
		||||
    $form_builder->submitForm($this, $form_state);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\IncompatibleComponentSchema;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests invalid render options for components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentInvalidReplacementTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['sdc_test_replacements_invalid'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that component replacement validates the schema compatibility.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionTheme(): void {
 | 
			
		||||
    $this->expectException(IncompatibleComponentSchema::class);
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,102 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\BubbleableMetadata;
 | 
			
		||||
use Drupal\Core\Render\RenderContext;
 | 
			
		||||
use Drupal\Core\Theme\ComponentNegotiator;
 | 
			
		||||
use Drupal\Core\Theme\ComponentPluginManager;
 | 
			
		||||
use Drupal\KernelTests\KernelTestBase;
 | 
			
		||||
use Symfony\Component\DomCrawler\Crawler;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a base class for component kernel tests.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ComponentKernelTestBase extends KernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'system',
 | 
			
		||||
    'user',
 | 
			
		||||
    'serialization',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Themes to install.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = [];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The component negotiator.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Drupal\Core\Theme\ComponentNegotiator
 | 
			
		||||
   */
 | 
			
		||||
  protected ComponentNegotiator $negotiator;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The component plugin manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Theme\ComponentPluginManager
 | 
			
		||||
   */
 | 
			
		||||
  protected ComponentPluginManager $manager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected function setUp(): void {
 | 
			
		||||
    parent::setUp();
 | 
			
		||||
    if (empty(static::$themes)) {
 | 
			
		||||
      throw new \Exception('You need to set the protected static $themes property on your test class, with the first item being the default theme.');
 | 
			
		||||
    }
 | 
			
		||||
    $this->container->get('theme_installer')->install(static::$themes);
 | 
			
		||||
    $this->installConfig('system');
 | 
			
		||||
 | 
			
		||||
    $system_theme_config = $this->container->get('config.factory')->getEditable('system.theme');
 | 
			
		||||
    $theme_name = reset(static::$themes);
 | 
			
		||||
    $system_theme_config
 | 
			
		||||
      ->set('default', $theme_name)
 | 
			
		||||
      ->save();
 | 
			
		||||
    $theme_manager = \Drupal::service('theme.manager');
 | 
			
		||||
    $active_theme = \Drupal::service('theme.initialization')->initTheme($theme_name);
 | 
			
		||||
    $theme_manager->setActiveTheme($active_theme);
 | 
			
		||||
 | 
			
		||||
    $this->negotiator = new ComponentNegotiator($theme_manager);
 | 
			
		||||
    $this->manager = \Drupal::service('plugin.manager.sdc');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Renders a component for testing sake.
 | 
			
		||||
   *
 | 
			
		||||
   * @param array $component
 | 
			
		||||
   *   Component render array.
 | 
			
		||||
   * @param \Drupal\Core\Render\BubbleableMetadata|null $metadata
 | 
			
		||||
   *   Bubble metadata.
 | 
			
		||||
   *
 | 
			
		||||
   * @return \Symfony\Component\DomCrawler\Crawler
 | 
			
		||||
   *   Crawler for introspecting the rendered component.
 | 
			
		||||
   */
 | 
			
		||||
  protected function renderComponentRenderArray(array $component, ?BubbleableMetadata $metadata = NULL): Crawler {
 | 
			
		||||
    $component = [
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#attributes' => [
 | 
			
		||||
        'id' => 'sdc-wrapper',
 | 
			
		||||
      ],
 | 
			
		||||
      'component' => $component,
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = $metadata ?: new BubbleableMetadata();
 | 
			
		||||
    $context = new RenderContext();
 | 
			
		||||
    $renderer = \Drupal::service('renderer');
 | 
			
		||||
    $output = $renderer->executeInRenderContext($context, fn () => $renderer->render($component));
 | 
			
		||||
    if (!$context->isEmpty()) {
 | 
			
		||||
      $metadata->addCacheableDependency($context->pop());
 | 
			
		||||
    }
 | 
			
		||||
    return new Crawler((string) $output);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,81 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the component negotiator.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Theme\ComponentNegotiator
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentNegotiatorTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = [
 | 
			
		||||
    'sdc_test',
 | 
			
		||||
    'sdc_test_replacements',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Themes to install.
 | 
			
		||||
   *
 | 
			
		||||
   * @var string[]
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = [
 | 
			
		||||
    'sdc_theme_test_enforce_schema', 'sdc_theme_test',
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @covers ::negotiate
 | 
			
		||||
   */
 | 
			
		||||
  public function testNegotiate(): void {
 | 
			
		||||
    $data = [
 | 
			
		||||
      ['sdc_test:my-banner', NULL],
 | 
			
		||||
      ['sdc_theme_test:my-card', 'sdc_theme_test_enforce_schema:my-card'],
 | 
			
		||||
      [
 | 
			
		||||
        'sdc_test:my-button',
 | 
			
		||||
        'sdc_test_replacements:my-button',
 | 
			
		||||
      ],
 | 
			
		||||
        ['invalid:component', NULL],
 | 
			
		||||
        ['invalid^component', NULL],
 | 
			
		||||
        ['', NULL],
 | 
			
		||||
    ];
 | 
			
		||||
    array_walk($data, function ($test_input) {
 | 
			
		||||
      [$requested_id, $expected_id] = $test_input;
 | 
			
		||||
      $negotiated_id = $this->negotiator->negotiate(
 | 
			
		||||
        $requested_id,
 | 
			
		||||
        $this->manager->getDefinitions(),
 | 
			
		||||
      );
 | 
			
		||||
      $this->assertSame($expected_id, $negotiated_id);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests rendering components with component replacement.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderWithReplacements(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button') }}",
 | 
			
		||||
      '#context' => ['text' => 'Like!', 'iconType' => 'like'],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button[data-component-id="sdc_test_replacements:my-button"]'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button .sdc-id:contains("sdc_test_replacements:my-button")'));
 | 
			
		||||
 | 
			
		||||
    // Now test component replacement on themes.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:my-card') }}",
 | 
			
		||||
      '#context' => ['header' => 'Foo bar'],
 | 
			
		||||
      '#variant' => 'horizontal',
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper .component--my-card--replaced__body'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the node visitor.
 | 
			
		||||
 *
 | 
			
		||||
 * @coversDefaultClass \Drupal\Core\Template\ComponentNodeVisitor
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentNodeVisitorTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_other_node_visitor'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  const DEBUG_COMPONENT_ID_PATTERN = '/<!-- ([\n\s\S]*) Component start: ([\SA-Za-z+-:]+) -->/';
 | 
			
		||||
  const DEBUG_VARIANT_ID_PATTERN = '/<!-- [\n\s\S]* with variant: "([\SA-Za-z+-]+)" -->/';
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that other visitors can modify Twig nodes.
 | 
			
		||||
   */
 | 
			
		||||
  public function testOtherVisitorsCanModifyTwigNodes(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{% embed('sdc_theme_test_base:my-card-no-schema') %}{% block card_body %}Foo bar{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $this->renderComponentRenderArray($build);
 | 
			
		||||
 | 
			
		||||
    // If this is reached, the test passed.
 | 
			
		||||
    $this->assertTrue(TRUE);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test debug output for sdc components with component id and variant.
 | 
			
		||||
   */
 | 
			
		||||
  public function testDebugRendersComponentStartWithVariant(): void {
 | 
			
		||||
    // Enable twig theme debug to ensure that any
 | 
			
		||||
    // changes to theme debugging format force checking
 | 
			
		||||
    // that the auto paragraph filter continues to be applied
 | 
			
		||||
    // correctly.
 | 
			
		||||
    $twig = \Drupal::service('twig');
 | 
			
		||||
    $twig->enableDebug();
 | 
			
		||||
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_theme_test:my-card',
 | 
			
		||||
      '#variant' => 'vertical',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'header' => 'My header',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'card_body' => 'Foo bar',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $content = $crawler->html();
 | 
			
		||||
 | 
			
		||||
    $matches = [];
 | 
			
		||||
    \preg_match_all(self::DEBUG_COMPONENT_ID_PATTERN, $content, $matches);
 | 
			
		||||
    $this->assertSame($matches[2][0], 'sdc_theme_test:my-card');
 | 
			
		||||
 | 
			
		||||
    \preg_match_all(self::DEBUG_VARIANT_ID_PATTERN, $content, $matches);
 | 
			
		||||
    $this->assertSame($matches[1][0], 'vertical');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests discovery of components in a theme being installed or uninstalled.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentPluginManagerCachedDiscoveryTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['stark'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Tests cached component plugin discovery on theme install and uninstall.
 | 
			
		||||
   */
 | 
			
		||||
  public function testComponentDiscoveryOnThemeInstall(): void {
 | 
			
		||||
    // Component in sdc_theme should not be found without sdc_theme installed.
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayNotHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
 | 
			
		||||
    // Component in sdc_theme should be found once sdc_theme is installed.
 | 
			
		||||
    \Drupal::service('theme_installer')->install(['sdc_theme_test']);
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
 | 
			
		||||
    // Component in sdc_theme should not be found once sdc_theme is uninstalled.
 | 
			
		||||
    \Drupal::service('theme_installer')->uninstall(['sdc_theme_test']);
 | 
			
		||||
    $definitions = \Drupal::service('plugin.manager.sdc')->getDefinitions();
 | 
			
		||||
    $this->assertArrayNotHasKey('sdc_theme_test:bar', $definitions);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\ComponentNotFoundException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the component plugin manager.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentPluginManagerTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_test', 'sdc_test_replacements'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that components render correctly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testFindEmptyMetadataFile(): void {
 | 
			
		||||
    // Test that empty component metadata files are valid, since there is no
 | 
			
		||||
    // required property.
 | 
			
		||||
    $this->assertNotEmpty(
 | 
			
		||||
      $this->manager->find('sdc_theme_test:bar'),
 | 
			
		||||
    );
 | 
			
		||||
    // Test that if the folder name does not match the machine name, the
 | 
			
		||||
    // component is still available.
 | 
			
		||||
    $this->assertNotEmpty(
 | 
			
		||||
      $this->manager->find('sdc_theme_test:foo'),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Test that the machine name is grabbed from the *.component.yml.
 | 
			
		||||
   *
 | 
			
		||||
   * And not from the enclosing directory.
 | 
			
		||||
   */
 | 
			
		||||
  public function testMismatchingFolderName(): void {
 | 
			
		||||
    $this->expectException(ComponentNotFoundException::class);
 | 
			
		||||
    $this->manager->find('sdc_theme_test:mismatching-folder-name');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\InvalidComponentException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests invalid render options for components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentRenderInvalidTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['sdc_test_invalid'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['starterkit_theme'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components in modules without schema fail validation.
 | 
			
		||||
   *
 | 
			
		||||
   * The module sdc_test_invalid contains the my-card-no-schema component. This
 | 
			
		||||
   * component does not have schema definitions.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionModule(): void {
 | 
			
		||||
    $this->expectException(InvalidComponentException::class);
 | 
			
		||||
    $this->expectExceptionMessage('The component "sdc_test_invalid:my-card-no-schema" does not provide schema information. Schema definitions are mandatory for components declared in modules. For components declared in themes, schema definitions are only mandatory if the "enforce_prop_schemas" key is set to "true" in the theme info file.');
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components in modules without schema fail validation.
 | 
			
		||||
   *
 | 
			
		||||
   * The theme sdc_theme_test_enforce_schema_invalid is set as enforcing schemas
 | 
			
		||||
   * but provides a component without schema.
 | 
			
		||||
   */
 | 
			
		||||
  public function testInvalidDefinitionTheme(): void {
 | 
			
		||||
    \Drupal::service('theme_installer')->install(['sdc_theme_test_enforce_schema_invalid']);
 | 
			
		||||
    $active_theme = \Drupal::service('theme.initialization')->initTheme('sdc_theme_test_enforce_schema_invalid');
 | 
			
		||||
    \Drupal::service('theme.manager')->setActiveTheme($active_theme);
 | 
			
		||||
    $this->expectException(InvalidComponentException::class);
 | 
			
		||||
    $this->manager->getDefinitions();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,384 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace Drupal\KernelTests\Components;
 | 
			
		||||
 | 
			
		||||
use Drupal\Core\Render\BubbleableMetadata;
 | 
			
		||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
 | 
			
		||||
use Drupal\Core\Template\Attribute;
 | 
			
		||||
use Drupal\Core\Theme\ComponentPluginManager;
 | 
			
		||||
use Drupal\Core\Render\Component\Exception\InvalidComponentDataException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Tests the correct rendering of components.
 | 
			
		||||
 *
 | 
			
		||||
 * @group sdc
 | 
			
		||||
 */
 | 
			
		||||
class ComponentRenderTest extends ComponentKernelTestBase {
 | 
			
		||||
 | 
			
		||||
  use StringTranslationTrait;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $modules = ['system', 'sdc_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  protected static $themes = ['sdc_theme_test'];
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using a component with an include and default context.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderIncludeDefaultContent(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{% embed('sdc_theme_test_base:my-card-no-schema') %}{% block card_body %}Foo bar{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"] .component--my-card-no-schema__body:contains("Foo bar")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using a component with an include and no default context.
 | 
			
		||||
   *
 | 
			
		||||
   * This covers passing a render array to a 'string' prop, and mapping the
 | 
			
		||||
   * prop to a context variable.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderIncludeDataMapping(): void {
 | 
			
		||||
    $content = [
 | 
			
		||||
      'label' => [
 | 
			
		||||
        '#type' => 'html_tag',
 | 
			
		||||
        '#tag' => 'span',
 | 
			
		||||
        '#value' => 'Another button ç',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external' }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper button:contains("Another button ç")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Render a card with slots that include a CTA component.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderEmbedWithNested(): void {
 | 
			
		||||
    $content = [
 | 
			
		||||
      'heading' => [
 | 
			
		||||
        '#type' => 'html_tag',
 | 
			
		||||
        '#tag' => 'span',
 | 
			
		||||
        '#value' => 'Just a link',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{% embed 'sdc_theme_test:my-card' with { variant: 'horizontal', header: 'Card header', content: content } only %}{% block card_body %}This is a card with a CTA {{ include('sdc_test:my-cta', { text: content.heading, href: 'https://www.example.org', target: '_blank' }, with_context = false) }}{% endblock %}{% endembed %}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] h2.component--my-card__header:contains("Card header")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body:contains("This is a card with a CTA")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body a[data-component-id="sdc_test:my-cta"]:contains("Just a link")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test:my-card"] .component--my-card__body a[data-component-id="sdc_test:my-cta"][href="https://www.example.org"][target="_blank"]'));
 | 
			
		||||
 | 
			
		||||
    // Now render a component and assert it contains the debug comments.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => [
 | 
			
		||||
          '#type' => 'html_tag',
 | 
			
		||||
          '#tag' => 'p',
 | 
			
		||||
          '#value' => 'This is the contents of the banner body.',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = new BubbleableMetadata();
 | 
			
		||||
    $this->renderComponentRenderArray($build, $metadata);
 | 
			
		||||
    $this->assertEquals(['core/components.sdc_test--my-cta', 'core/components.sdc_test--my-banner'], $metadata->getAttachments()['library']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Check using the libraryOverrides.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderLibraryOverrides(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:lib-overrides') }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $metadata = new BubbleableMetadata();
 | 
			
		||||
    $this->renderComponentRenderArray($build, $metadata);
 | 
			
		||||
    $this->assertEquals(['core/components.sdc_theme_test--lib-overrides'], $metadata->getAttachments()['library']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the schema violations are reported properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderPropValidation(): void {
 | 
			
		||||
    // 1. Violates the minLength for the text property.
 | 
			
		||||
    $content = ['label' => '1'];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external' }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 2. Violates the required header property.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test:my-card', with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure fuzzy coercing of arrays and objects works properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderArrayObjectTypeCast(): void {
 | 
			
		||||
    $content = ['test' => []];
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:array-to-object', { testProp: content.test }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->fail('Empty array was not converted to object');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures that including an invalid component creates an error.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderNonExistingComponent(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:INVALID', with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    try {
 | 
			
		||||
      $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->fail('Invalid prop did not cause an exception');
 | 
			
		||||
    }
 | 
			
		||||
    catch (\Throwable) {
 | 
			
		||||
      $this->addToAssertionCount(1);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the attributes are merged properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderAttributeMerging(): void {
 | 
			
		||||
    $content = ['label' => 'I am a labels'];
 | 
			
		||||
    // 1. Check that if it exists Attribute object in the 'attributes' prop, you
 | 
			
		||||
    // get them merged.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [
 | 
			
		||||
        'content' => $content,
 | 
			
		||||
        'attributes' => new Attribute(['data-merged-attributes' => 'yes']),
 | 
			
		||||
      ],
 | 
			
		||||
      '#template' => "{{ include('sdc_test:my-button', { text: content.label, iconType: 'external', attributes: attributes }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-merged-attributes="yes"][data-component-id="sdc_test:my-button"]'), $crawler->outerHtml());
 | 
			
		||||
    // 2. Check that if the 'attributes' exists, but there is some other data
 | 
			
		||||
    // type, then we don't touch it.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => [
 | 
			
		||||
        'content' => $content,
 | 
			
		||||
        'attributes' => 'hard-coded-attr',
 | 
			
		||||
      ],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test_base:my-card-no-schema', { header: content.label, attributes: attributes }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    // The default data attribute should be missing.
 | 
			
		||||
    $this->assertEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"]'), $crawler->outerHtml());
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [hard-coded-attr]'), $crawler->outerHtml());
 | 
			
		||||
    // 3. Check that if the 'attributes' is empty, we get the defaults.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'inline_template',
 | 
			
		||||
      '#context' => ['content' => $content],
 | 
			
		||||
      '#template' => "{{ include('sdc_theme_test_base:my-card-no-schema', { header: content.label }, with_context = false) }}",
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    // The default data attribute should not be missing.
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_theme_test_base:my-card-no-schema"]'), $crawler->outerHtml());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures the alter callbacks work properly.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderElementAlters(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#propsAlter' => [
 | 
			
		||||
        fn ($props) => [...$props, 'heading' => 'I am another banner'],
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => [
 | 
			
		||||
          '#type' => 'html_tag',
 | 
			
		||||
          '#tag' => 'p',
 | 
			
		||||
          '#value' => 'This is the contents of the banner body.',
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
      '#slotsAlter' => [
 | 
			
		||||
        static fn ($slots) => [...$slots, 'banner_body' => ['#markup' => '<h2>Just something else.</h2>']],
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--header h3:contains("I am another banner")'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--body:contains("Just something else.")'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that the slots allow a render array or a scalar when using the render element.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderSlots(): void {
 | 
			
		||||
    $slots = [
 | 
			
		||||
      'This is the contents of the banner body.',
 | 
			
		||||
      [
 | 
			
		||||
        '#plain_text' => 'This is the contents of the banner body.',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    foreach ($slots as $slot) {
 | 
			
		||||
      $build = [
 | 
			
		||||
        '#type' => 'component',
 | 
			
		||||
        '#component' => 'sdc_test:my-banner',
 | 
			
		||||
        '#props' => [
 | 
			
		||||
          'heading' => 'I am a banner',
 | 
			
		||||
          'ctaText' => 'Click me',
 | 
			
		||||
          'ctaHref' => 'https://www.example.org',
 | 
			
		||||
          'ctaTarget' => '',
 | 
			
		||||
        ],
 | 
			
		||||
        '#slots' => [
 | 
			
		||||
          'banner_body' => $slot,
 | 
			
		||||
        ],
 | 
			
		||||
      ];
 | 
			
		||||
      $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
      $this->assertNotEmpty($crawler->filter('#sdc-wrapper [data-component-id="sdc_test:my-banner"] .component--my-banner--body:contains("This is the contents of the banner body.")'));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that the slots throw an error for invalid slots.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderInvalidSlot(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-banner',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'heading' => 'I am a banner',
 | 
			
		||||
        'ctaText' => 'Click me',
 | 
			
		||||
        'ctaHref' => 'https://www.example.org',
 | 
			
		||||
        'ctaTarget' => '',
 | 
			
		||||
      ],
 | 
			
		||||
      '#slots' => [
 | 
			
		||||
        'banner_body' => new \stdClass(),
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $this->expectException(InvalidComponentDataException::class);
 | 
			
		||||
    $this->expectExceptionMessage('Unable to render component "sdc_test:my-banner". A render array or a scalar is expected for the slot "banner_body" when using the render element with the "#slots" property');
 | 
			
		||||
    $this->renderComponentRenderArray($build);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components can have 0 props.
 | 
			
		||||
   */
 | 
			
		||||
  public function testRenderEmptyProps(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:no-props',
 | 
			
		||||
      '#props' => [],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertEquals(
 | 
			
		||||
      $crawler->filter('#sdc-wrapper')->innerText(),
 | 
			
		||||
      'This is a test string.'
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensure that components variants render.
 | 
			
		||||
   */
 | 
			
		||||
  public function testVariants(): void {
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-cta',
 | 
			
		||||
      '#variant' => 'primary',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'text' => 'Test link',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta"][data-component-variant="primary"][class*="my-cta-primary"]'));
 | 
			
		||||
 | 
			
		||||
    // If there were an existing prop named variant, we don't override that for BC reasons.
 | 
			
		||||
    $build = [
 | 
			
		||||
      '#type' => 'component',
 | 
			
		||||
      '#component' => 'sdc_test:my-cta-with-variant-prop',
 | 
			
		||||
      '#variant' => 'tertiary',
 | 
			
		||||
      '#props' => [
 | 
			
		||||
        'text' => 'Test link',
 | 
			
		||||
        'variant' => 'secondary',
 | 
			
		||||
      ],
 | 
			
		||||
    ];
 | 
			
		||||
    $crawler = $this->renderComponentRenderArray($build);
 | 
			
		||||
    $this->assertEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta-with-variant-prop"][data-component-variant="tertiary"]'));
 | 
			
		||||
    $this->assertNotEmpty($crawler->filter('#sdc-wrapper a[data-component-id="sdc_test:my-cta-with-variant-prop"][data-component-variant="secondary"]'));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Ensures some key aspects of the plugin definition are correctly computed.
 | 
			
		||||
   *
 | 
			
		||||
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 | 
			
		||||
   */
 | 
			
		||||
  public function testPluginDefinition(): void {
 | 
			
		||||
    $plugin_manager = \Drupal::service('plugin.manager.sdc');
 | 
			
		||||
    assert($plugin_manager instanceof ComponentPluginManager);
 | 
			
		||||
    $definition = $plugin_manager->getDefinition('sdc_test:my-banner');
 | 
			
		||||
    $this->assertSame('my-banner', $definition['machineName']);
 | 
			
		||||
    $this->assertStringEndsWith('system/tests/modules/sdc_test/components/my-banner', $definition['path']);
 | 
			
		||||
    $this->assertEquals(['core/drupal'], $definition['library']['dependencies']);
 | 
			
		||||
    $this->assertNotEmpty($definition['library']['css']['component']);
 | 
			
		||||
    $this->assertSame('my-banner.twig', $definition['template']);
 | 
			
		||||
    $this->assertNotEmpty($definition['documentation']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user