diff --git a/src/Plugin/UiPatterns/PropType/SlotPropType.php b/src/Plugin/UiPatterns/PropType/SlotPropType.php index a6fade789b04ef77c9b9aa02d24fc19d26473fe9..f81853fad2366d9d3d64f1daa6c0cdbc0b35d8e1 100644 --- a/src/Plugin/UiPatterns/PropType/SlotPropType.php +++ b/src/Plugin/UiPatterns/PropType/SlotPropType.php @@ -6,6 +6,7 @@ namespace Drupal\ui_patterns\Plugin\UiPatterns\PropType; use Drupal\Component\Render\MarkupInterface; use Drupal\Core\Render\Element; +use Drupal\Core\Render\Markup; use Drupal\Core\Render\RenderableInterface; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\ui_patterns\Attribute\PropType; @@ -33,44 +34,36 @@ class SlotPropType extends PropTypePluginBase { return self::convertObject($value); } if (is_string($value)) { - return ['#markup' => $value]; + return Markup::create($value); } - if (is_array($value) && self::isMappingNotRenderable($value)) { + if (is_array($value) && empty(Element::properties($value))) { // Twig `is sequence` and `is mapping `tests are not useful when a list // of renderables has mapping keys (non consecutive, strings) instead of // sequence (integer, consecutive) keys. For example a list of blocks // from page layout or layout builder: each block is keyed by its UUID. // So, transform this list of renderables to a proper Twig sequence. - return array_values($value); + $value = array_map(static fn($item) => self::normalize($item), array_is_list($value) ? $value : array_values($value)); + return (count($value) === 1) ? reset($value) : $value; } return $value; } - /** - * Is the array a mapping array but not a renderable array? - */ - protected static function isMappingNotRenderable(array $value): bool { - return count($value) > 1 && !array_is_list($value) && empty(Element::properties($value)); - } - /** * Convert PHP objects to render array. */ - protected static function convertObject(object $value): array { + protected static function convertObject(object $value): mixed { if ($value instanceof RenderableInterface) { $value = $value->toRenderable(); } if ($value instanceof MarkupInterface) { - return [ - '#markup' => (string) $value, - ]; + return $value; } elseif ($value instanceof \Stringable) { return [ '#plain_text' => (string) $value, ]; } - return []; + return $value; } /** diff --git a/tests/modules/ui_patterns_test/components/test-component/test-component.twig b/tests/modules/ui_patterns_test/components/test-component/test-component.twig index 10ff70a2dafc23a899d899d1637b6f1c5dcf2caa..19530d8ec5c47a8b888099ca2569da8ee503e328 100644 --- a/tests/modules/ui_patterns_test/components/test-component/test-component.twig +++ b/tests/modules/ui_patterns_test/components/test-component/test-component.twig @@ -1,4 +1,4 @@ -<div{{ attributes.addClass(["ui-patterns-test-component"]) }}> +<div{{ attributes.addClass(['ui-patterns-test-component']) }}> <div class="ui-patterns-props-string"> {{ string }} </div> diff --git a/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.component.yml b/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.component.yml new file mode 100644 index 0000000000000000000000000000000000000000..61ff349ea8c2f70f3bbb9ae041064a20c97f206a --- /dev/null +++ b/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.component.yml @@ -0,0 +1,14 @@ +name: "UI Patterns Test wrapper component" +props: + type: object + properties: + string: + title: "String" + type: "string" + base_id: + title: "base id" + $ref: "ui-patterns://identifier" +slots: + slot: + title: "slot" + description: "slot" diff --git a/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.twig b/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.twig new file mode 100644 index 0000000000000000000000000000000000000000..cd9040f1d185c2a1203893662a1395166ec78883 --- /dev/null +++ b/tests/modules/ui_patterns_test/components/test-form-component/test-form-component.twig @@ -0,0 +1,5 @@ +{% set base_id = base_id|default('ui-patterns-test-form') %} +<form {{ attributes.addClass(['ui-patterns-test-form', base_id]) }}> + <textarea id="{{ base_id }}-textarea">{{ slot }}</textarea> + <input id="{{ base_id }}-input" type="text" name="name" value="{{ string }}" /> +</form> diff --git a/tests/src/Kernel/SlotNormalizationTest.php b/tests/src/Kernel/SlotNormalizationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a797049a04615a4e805ba2ce0bdf98791263af20 --- /dev/null +++ b/tests/src/Kernel/SlotNormalizationTest.php @@ -0,0 +1,104 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\ui_patterns\Kernel; + +use Drupal\Core\Render\Markup; +use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\ui_patterns\Traits\TestDataTrait; + +/** + * Test slot normalization. + * + * @group ui_patterns + */ +class SlotNormalizationTest extends KernelTestBase { + + use TestDataTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'system', + 'user', + 'text', + 'field', + 'node', + 'ui_patterns', + 'ui_patterns_test', + 'datetime', + 'filter', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->installSchema('node', 'node_access'); + $this->installEntitySchema('node'); + $this->installEntitySchema('user'); + $this->installConfig(['system', 'filter']); + } + + /** + * Test slot normalization. + */ + public function testSlotNormalization() : void { + $tested_values = [ + ["my slot"], + "my slot", + Markup::create("my slot"), + ["#markup" => "my slot"], + ["a" => ["#markup" => "my "], "b" => ["#markup" => "slot"]], + ["aa" => "my slot"], + t("my slot"), + ["#type" => "inline_template", "#template" => "my slot"], + ]; + $render_array_test = [ + "#type" => "inline_template", + "#template" => "{{ include('ui_patterns_test:test-component', {slot: injected}) }}", + "#context" => ["injected" => ""], + ]; + foreach ($tested_values as $tested_value) { + $this->assertExpectedOutput( + [ + "rendered_value" => "my slot", + "assert" => "assertStringContainsString", + ], + array_merge($render_array_test, ["#context" => ["injected" => $tested_value]]) + ); + } + } + + /** + * Test slot normalization. + */ + public function testNestedComponentWithForm() : void { + // Test nested component with form. + $render_array_test = [ + "#type" => "inline_template", + "#template" => " + {% set comp_form = include('ui_patterns_test:test-form-component', {}) %} + {{ include('ui_patterns_test:test-component', {slot: comp_form}) }}", + "#context" => [], + ]; + $this->assertExpectedOutput( + [ + "rendered_value" => "<input ", + "assert" => "assertStringContainsString", + ], + $render_array_test + ); + $this->assertExpectedOutput( + [ + "rendered_value" => "<form ", + "assert" => "assertStringContainsString", + ], + $render_array_test + ); + } + +} diff --git a/tests/src/Kernel/TwigVisitorTest.php b/tests/src/Kernel/TwigVisitorTest.php index e4738078e67bbe5bea700f27228e0d488ad805b0..5f30bb4e561cb0b9e9c4476f985879e038eb506d 100644 --- a/tests/src/Kernel/TwigVisitorTest.php +++ b/tests/src/Kernel/TwigVisitorTest.php @@ -8,7 +8,7 @@ use Drupal\KernelTests\KernelTestBase; use Drupal\Tests\ui_patterns\Traits\TestDataTrait; /** - * Base class to test source plugins. + * Test node visitor. * * @group ui_patterns */ @@ -43,9 +43,9 @@ class TwigVisitorTest extends KernelTestBase { } /** - * Test attributes. + * Test different twig usages. */ - public function testAttributes() : void { + public function testTwigIntegration() : void { $default_context = [ 'prop_string' => $this->randomMachineName(),