Skip to content
Snippets Groups Projects
Commit 2ac2823c authored by Mikael Meulle's avatar Mikael Meulle
Browse files

Issue #3510596 by just_like_good_vibes, goz: Improve prop value rendering for booleans

parent 4e187eda
No related branches found
No related tags found
1 merge request!358Issue #3510596 by goz, just_like_good_vibes: [2.0.4] Improve prop value rendering for booleans
Pipeline #457458 passed
...@@ -98,19 +98,39 @@ class ComponentElementBuilder implements TrustedCallbackInterface { ...@@ -98,19 +98,39 @@ class ComponentElementBuilder implements TrustedCallbackInterface {
} }
} }
else { else {
if (!empty($data) || $prop_type->getPluginId() === 'attributes') { if (!static::isPropValueEmpty($data, $prop_type)) {
// 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.
$build['#props'][$prop_or_slot_id] = $data; $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. * Update the build array for a configured source on a prop/slot.
* *
......
...@@ -28,7 +28,7 @@ class BooleanPropType extends PropTypePluginBase { ...@@ -28,7 +28,7 @@ class BooleanPropType extends PropTypePluginBase {
/** /**
* {@inheritdoc} * {@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)) { if (is_bool($value)) {
return $value; return $value;
} }
...@@ -37,7 +37,7 @@ class BooleanPropType extends PropTypePluginBase { ...@@ -37,7 +37,7 @@ class BooleanPropType extends PropTypePluginBase {
$value = (int) $value; $value = (int) $value;
return (bool) $value; return (bool) $value;
} }
return (bool) $value; return ($value !== NULL) ? (bool) $value : NULL;
} }
} }
...@@ -37,7 +37,7 @@ class StringPropType extends PropTypePluginBase { ...@@ -37,7 +37,7 @@ class StringPropType extends PropTypePluginBase {
'url' => $value, 'url' => $value,
'identifier' => $value, 'identifier' => $value,
'string' => $value, 'string' => $value,
'default' => (string) $value, default => (string) $value,
}; };
} }
......
...@@ -29,6 +29,12 @@ props: ...@@ -29,6 +29,12 @@ props:
boolean: boolean:
title: "Boolean" title: "Boolean"
$ref: "ui-patterns://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: links:
title: "Links" title: "Links"
$ref: "ui-patterns://links" $ref: "ui-patterns://links"
......
...@@ -25,6 +25,18 @@ ...@@ -25,6 +25,18 @@
<div class="ui-patterns-props-boolean"> <div class="ui-patterns-props-boolean">
{{ boolean }} {{ boolean }}
</div> </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"> <div class="ui-patterns-props-links">
{% for item in links %} {% for item in links %}
{% if item.url %} {% if item.url %}
......
...@@ -34,6 +34,24 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { ...@@ -34,6 +34,24 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase {
$this->runRenderPropTest('boolean', ["value" => $value, "rendered_value" => $rendered_value]); $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]);
}
/**
* 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]);
}
/** /**
* Provides data for testNormalization. * Provides data for testNormalization.
*/ */
...@@ -81,4 +99,44 @@ class BooleanPropTypeTest extends PropTypeNormalizationTestBase { ...@@ -81,4 +99,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>',
],
];
}
} }
...@@ -54,12 +54,17 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase { ...@@ -54,12 +54,17 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase {
/** /**
* Run tests with a given prop. * Run tests with a given prop.
* *
* @param string $prop * @param string|null $prop
* The prop to test. * The prop to test or NULL for no prop.
* @param array $tested_value * @param array $tested_value
* The tested value. * The tested value.
*/ */
protected function runRenderPropTest(string $prop, array $tested_value) : void { 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 = [ $expectedOutput = [
"rendered_value" => $tested_value["rendered_value"], "rendered_value" => $tested_value["rendered_value"],
"assert" => $tested_value["assert"] ?? "assertStringContainsString", "assert" => $tested_value["assert"] ?? "assertStringContainsString",
...@@ -68,19 +73,35 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase { ...@@ -68,19 +73,35 @@ abstract class PropTypeNormalizationTestBase extends KernelTestBase {
if (!empty($exception_class)) { if (!empty($exception_class)) {
$this->expectException($exception_class); $this->expectException($exception_class);
} }
$this->assertExpectedOutput($expectedOutput, [ if (!empty($prop)) {
"#type" => "inline_template", $this->assertExpectedOutput($expectedOutput, [
"#template" => sprintf("{{ include('ui_patterns_test:test-component', {%s: injected}) }}", $prop), "#type" => "inline_template",
"#context" => ["injected" => $tested_value["value"]], "#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)) { if (!empty($exception_class)) {
$this->expectException($exception_class); $this->expectException($exception_class);
} }
$this->assertExpectedOutput($expectedOutput, [ if (!empty($prop)) {
"#type" => "component", $this->assertExpectedOutput($expectedOutput, [
'#component' => 'ui_patterns_test:test-component', "#type" => "component",
"#props" => [$prop => $tested_value["value"]], '#component' => 'ui_patterns_test:test-component',
]); "#props" => [$prop => $tested_value["value"]],
]);
}
else {
$this->assertExpectedOutput($expectedOutput, [
"#type" => "component",
'#component' => 'ui_patterns_test:test-component',
]);
}
} }
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment