From 8995e3be0fbf0f2662e93b940919e9a4956800e8 Mon Sep 17 00:00:00 2001 From: just_like_good_vibes <mickael@meulle.com> Date: Mon, 24 Mar 2025 12:35:00 +0100 Subject: [PATCH 1/3] updated --- src/Element/ComponentElementBuilder.php | 36 ++++++++--- .../UiPatterns/PropType/BooleanPropType.php | 4 +- .../UiPatterns/PropType/StringPropType.php | 2 +- .../test-component.component.yml | 6 ++ .../test-component/test-component.twig | 12 ++++ .../BooleanPropTypeTest.php | 64 +++++++++++++++++++ .../Kernel/PropTypeNormalizationTestBase.php | 42 ++++++++---- 7 files changed, 142 insertions(+), 24 deletions(-) diff --git a/src/Element/ComponentElementBuilder.php b/src/Element/ComponentElementBuilder.php index 23a3a1e8..67bf9e2d 100644 --- a/src/Element/ComponentElementBuilder.php +++ b/src/Element/ComponentElementBuilder.php @@ -98,19 +98,39 @@ class ComponentElementBuilder implements TrustedCallbackInterface { } } else { - if (!empty($data) || $prop_type->getPluginId() === 'attributes') { - // For JSON Schema validator, empty value is not the same as missing - // value, and we want to prevent some of the prop types rules to be - // applied on empty values: string pattern, string format, - // enum, number min/max... - // However, we don't remove empty attributes to avoid an error with - // Drupal\Core\Template\TwigExtension::createAttribute() when themers - // forget to use the default({}) filter. + if (!static::isPropValueEmpty($data, $prop_type)) { $build['#props'][$prop_or_slot_id] = $data; } } } + /** + * Determine if a prop value is empty. + * + * @param mixed $data + * The prop value. + * @param \Drupal\ui_patterns\PropTypeInterface $prop_type + * Target prop type. + * + * @return bool + * TRUE if the prop value is empty, FALSE otherwise. + */ + protected static function isPropValueEmpty(mixed $data, PropTypeInterface $prop_type): bool { + // For JSON Schema validator, empty value is not the same as missing + // value, and we want to prevent some of the prop types rules to be + // applied on empty values: string pattern, string format, + // enum, number min/max... + // However, we don't remove empty attributes to avoid an error with + // Drupal\Core\Template\TwigExtension::createAttribute() when themers + // forget to use the default({}) filter. + // For boolean values, we only remove NULL values. + return match ($prop_type->getPluginId()) { + 'attributes' => FALSE, + 'boolean' => ($data === NULL), + default => empty($data), + }; + } + /** * Update the build array for a configured source on a prop/slot. * diff --git a/src/Plugin/UiPatterns/PropType/BooleanPropType.php b/src/Plugin/UiPatterns/PropType/BooleanPropType.php index 2b9d2bba..c34ce92c 100644 --- a/src/Plugin/UiPatterns/PropType/BooleanPropType.php +++ b/src/Plugin/UiPatterns/PropType/BooleanPropType.php @@ -28,7 +28,7 @@ class BooleanPropType extends PropTypePluginBase { /** * {@inheritdoc} */ - public static function normalize(mixed $value, ?array $definition = NULL): bool { + public static function normalize(mixed $value, ?array $definition = NULL): ?bool { if (is_bool($value)) { return $value; } @@ -37,7 +37,7 @@ class BooleanPropType extends PropTypePluginBase { $value = (int) $value; return (bool) $value; } - return (bool) $value; + return ($value !== NULL) ? (bool) $value : NULL; } } diff --git a/src/Plugin/UiPatterns/PropType/StringPropType.php b/src/Plugin/UiPatterns/PropType/StringPropType.php index e3581382..6e83429d 100644 --- a/src/Plugin/UiPatterns/PropType/StringPropType.php +++ b/src/Plugin/UiPatterns/PropType/StringPropType.php @@ -37,7 +37,7 @@ class StringPropType extends PropTypePluginBase { 'url' => $value, 'identifier' => $value, 'string' => $value, - 'default' => (string) $value, + default => (string) $value, }; } diff --git a/tests/modules/ui_patterns_test/components/test-component/test-component.component.yml b/tests/modules/ui_patterns_test/components/test-component/test-component.component.yml index 5315381b..66826dd4 100644 --- a/tests/modules/ui_patterns_test/components/test-component/test-component.component.yml +++ b/tests/modules/ui_patterns_test/components/test-component/test-component.component.yml @@ -29,6 +29,12 @@ props: boolean: title: "Boolean" $ref: "ui-patterns://boolean" + boolean_with_default_false: + title: "Boolean" + $ref: "ui-patterns://boolean" + boolean_with_default_true: + title: "Boolean" + $ref: "ui-patterns://boolean" links: title: "Links" $ref: "ui-patterns://links" 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 6af3c38a..e1a8976c 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 @@ -25,6 +25,18 @@ <div class="ui-patterns-props-boolean"> {{ boolean }} </div> + <div class="ui-patterns-props-boolean_with_default_false"> + {% if (boolean_with_default_false is not defined) or (boolean_with_default_false is null) %} + {% set boolean_with_default_false = false %} + {% endif %} + {{ boolean_with_default_false }} + </div> + <div class="ui-patterns-props-boolean_with_default_true"> + {% if (boolean_with_default_true is not defined) or (boolean_with_default_true is null) %} + {% set boolean_with_default_true = true %} + {% endif %} + {{ boolean_with_default_true }} + </div> <div class="ui-patterns-props-links"> {% for item in links %} {% if item.url %} diff --git a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php index 63417c9a..f7384a63 100644 --- a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php +++ b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php @@ -34,6 +34,30 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { $this->runRenderPropTest('boolean', ["value" => $value, "rendered_value" => $rendered_value]); } + /** + * Test rendered component with prop default false. + * + * @dataProvider renderingTestsDefaultFalse + */ + public function testRenderingDefaultFalse(mixed $value, mixed $rendered_value) : void { + $this->runRenderPropTest('boolean_with_default_false', ["value" => $value, "rendered_value" => $rendered_value]); + if ($value === TRUE) { + $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); + } + } + + /** + * Test rendered component with prop default true. + * + * @dataProvider renderingTestsDefaultTrue + */ + public function testRenderingDefaultTrue(mixed $value, mixed $rendered_value) : void { + $this->runRenderPropTest('boolean_with_default_true', ["value" => $value, "rendered_value" => $rendered_value]); + if ($value === TRUE) { + $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); + } + } + /** * Provides data for testNormalization. */ @@ -81,4 +105,44 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { ]; } + /** + * Provides data for testNormalization. + */ + public static function renderingTestsDefaultFalse() : array { + return [ + "null value" => [ + NULL, + '<div class="ui-patterns-props-boolean_with_default_false"></div>', + ], + "false value" => [ + FALSE, + '<div class="ui-patterns-props-boolean_with_default_false"></div>', + ], + "true value" => [ + TRUE, + '<div class="ui-patterns-props-boolean_with_default_false">1</div>', + ], + ]; + } + + /** + * Provides data for testNormalization. + */ + public static function renderingTestsDefaultTrue() : array { + return [ + "null value" => [ + NULL, + '<div class="ui-patterns-props-boolean_with_default_true">1</div>', + ], + "false value" => [ + FALSE, + '<div class="ui-patterns-props-boolean_with_default_true"></div>', + ], + "true value" => [ + TRUE, + '<div class="ui-patterns-props-boolean_with_default_true">1</div>', + ], + ]; + } + } diff --git a/tests/src/Kernel/PropTypeNormalizationTestBase.php b/tests/src/Kernel/PropTypeNormalizationTestBase.php index 1d1cfebd..90d8f31a 100644 --- a/tests/src/Kernel/PropTypeNormalizationTestBase.php +++ b/tests/src/Kernel/PropTypeNormalizationTestBase.php @@ -54,12 +54,12 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase { /** * Run tests with a given prop. * - * @param string $prop - * The prop to test. + * @param string|null $prop + * The prop to test or NULL for no prop. * @param array $tested_value * The tested value. */ - protected function runRenderPropTest(string $prop, array $tested_value) : void { + protected function runRenderPropTest(?string $prop, array $tested_value) : void { $expectedOutput = [ "rendered_value" => $tested_value["rendered_value"], "assert" => $tested_value["assert"] ?? "assertStringContainsString", @@ -68,19 +68,35 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase { if (!empty($exception_class)) { $this->expectException($exception_class); } - $this->assertExpectedOutput($expectedOutput, [ - "#type" => "inline_template", - "#template" => sprintf("{{ include('ui_patterns_test:test-component', {%s: injected}) }}", $prop), - "#context" => ["injected" => $tested_value["value"]], - ]); + if (!empty($prop)) { + $this->assertExpectedOutput($expectedOutput, [ + "#type" => "inline_template", + "#template" => sprintf("{{ include('ui_patterns_test:test-component', {%s: injected}) }}", $prop), + "#context" => ["injected" => $tested_value["value"]], + ]); + } + else { + $this->assertExpectedOutput($expectedOutput, [ + "#type" => "inline_template", + "#template" => "{{ include('ui_patterns_test:test-component', {}) }}", + ]); + } if (!empty($exception_class)) { $this->expectException($exception_class); } - $this->assertExpectedOutput($expectedOutput, [ - "#type" => "component", - '#component' => 'ui_patterns_test:test-component', - "#props" => [$prop => $tested_value["value"]], - ]); + if (!empty($prop)) { + $this->assertExpectedOutput($expectedOutput, [ + "#type" => "component", + '#component' => 'ui_patterns_test:test-component', + "#props" => [$prop => $tested_value["value"]], + ]); + } + else { + $this->assertExpectedOutput($expectedOutput, [ + "#type" => "component", + '#component' => 'ui_patterns_test:test-component', + ]); + } } } -- GitLab From 072dcad942803496b5593da5e6caef26d43dc589 Mon Sep 17 00:00:00 2001 From: just_like_good_vibes <mickael@meulle.com> Date: Mon, 24 Mar 2025 13:57:58 +0100 Subject: [PATCH 2/3] fixed bad test --- .../src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php index f7384a63..8449ea9b 100644 --- a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php +++ b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php @@ -41,7 +41,7 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { */ public function testRenderingDefaultFalse(mixed $value, mixed $rendered_value) : void { $this->runRenderPropTest('boolean_with_default_false', ["value" => $value, "rendered_value" => $rendered_value]); - if ($value === TRUE) { + if ($value === NULL) { $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); } } @@ -53,7 +53,7 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { */ public function testRenderingDefaultTrue(mixed $value, mixed $rendered_value) : void { $this->runRenderPropTest('boolean_with_default_true', ["value" => $value, "rendered_value" => $rendered_value]); - if ($value === TRUE) { + if ($value === NULL) { $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); } } -- GitLab From f853f13695d50c5c209fda802dd215afc45ac576 Mon Sep 17 00:00:00 2001 From: just_like_good_vibes <mickael@meulle.com> Date: Mon, 24 Mar 2025 14:00:59 +0100 Subject: [PATCH 3/3] improved tests with NULL value --- .../Kernel/PropTypeNormalization/BooleanPropTypeTest.php | 6 ------ tests/src/Kernel/PropTypeNormalizationTestBase.php | 5 +++++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php index 8449ea9b..7c5eec9c 100644 --- a/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php +++ b/tests/src/Kernel/PropTypeNormalization/BooleanPropTypeTest.php @@ -41,9 +41,6 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { */ public function testRenderingDefaultFalse(mixed $value, mixed $rendered_value) : void { $this->runRenderPropTest('boolean_with_default_false', ["value" => $value, "rendered_value" => $rendered_value]); - if ($value === NULL) { - $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); - } } /** @@ -53,9 +50,6 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { */ public function testRenderingDefaultTrue(mixed $value, mixed $rendered_value) : void { $this->runRenderPropTest('boolean_with_default_true', ["value" => $value, "rendered_value" => $rendered_value]); - if ($value === NULL) { - $this->runRenderPropTest(NULL, ["value" => $value, "rendered_value" => $rendered_value]); - } } /** diff --git a/tests/src/Kernel/PropTypeNormalizationTestBase.php b/tests/src/Kernel/PropTypeNormalizationTestBase.php index 90d8f31a..a73450ee 100644 --- a/tests/src/Kernel/PropTypeNormalizationTestBase.php +++ b/tests/src/Kernel/PropTypeNormalizationTestBase.php @@ -60,6 +60,11 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase { * The tested value. */ protected function runRenderPropTest(?string $prop, array $tested_value) : void { + if ($tested_value["value"] === NULL && !empty($prop)) { + // If the value injected is NULL + // we will also try with removing the prop. + $this->runRenderPropTest(NULL, $tested_value); + } $expectedOutput = [ "rendered_value" => $tested_value["rendered_value"], "assert" => $tested_value["assert"] ?? "assertStringContainsString", -- GitLab