Skip to content
Snippets Groups Projects
Commit 5c35b431 authored by Pierre Dureau's avatar Pierre Dureau
Browse files

Merge branch '3449653-only-twig-visitor' into '2.0.x'

Issue #3449653 by pdureau: Execute normalization when Twig include and embed are used

See merge request !267
parents 71517119 3d420a98
No related branches found
No related tags found
No related merge requests found
Pipeline #350533 failed
...@@ -5,11 +5,9 @@ declare(strict_types=1); ...@@ -5,11 +5,9 @@ declare(strict_types=1);
namespace Drupal\ui_patterns\Element; namespace Drupal\ui_patterns\Element;
use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Security\TrustedCallbackInterface; use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Theme\ComponentPluginManager; use Drupal\Core\Theme\ComponentPluginManager;
use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType; use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
use Drupal\ui_patterns\PropTypeAdapterPluginManager;
/** /**
* Our additions to the SDC render element. * Our additions to the SDC render element.
...@@ -19,27 +17,25 @@ class ComponentElementAlter implements TrustedCallbackInterface { ...@@ -19,27 +17,25 @@ class ComponentElementAlter implements TrustedCallbackInterface {
/** /**
* Constructs a ComponentElementAlter. * Constructs a ComponentElementAlter.
*/ */
public function __construct(protected ComponentPluginManager $componentPluginManager, protected PropTypeAdapterPluginManager $adaptersManager) { public function __construct(protected ComponentPluginManager $componentPluginManager) {}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public static function trustedCallbacks() { public static function trustedCallbacks() {
return ['alter']; return ['alter'];
} }
/** /**
* Alter SDC component element. * Alter SDC component element.
*
* The ::normalizeProps() methods logic has been moved to
* TwigExtension::normalizeProps() in order to be executed also when
* components are loaded from Twig include or embed.
*/ */
public function alter(array $element): array { public function alter(array $element): array {
$element = $this->normalizeSlots($element); $element = $this->normalizeSlots($element);
$component = $this->componentPluginManager->find($element['#component']);
$element = $this->normalizeProps($element, $component);
// Attributes prop must never be empty, to avoid the processing of SDC's
// ComponentsTwigExtension::mergeAdditionalRenderContext() which is adding
// an Attribute PHP object before running the validator.
$element["#props"]["attributes"]['data-component-id'] = $component->getPluginId();
$element = $this->processAttributesRenderProperty($element); $element = $this->processAttributesRenderProperty($element);
return $element; return $element;
} }
...@@ -64,32 +60,6 @@ class ComponentElementAlter implements TrustedCallbackInterface { ...@@ -64,32 +60,6 @@ class ComponentElementAlter implements TrustedCallbackInterface {
return $element; return $element;
} }
/**
* Normalize props.
*/
public function normalizeProps(array $element, Component $component): array {
$props = $component->metadata->schema['properties'] ?? [];
foreach ($element["#props"] as $prop_id => $prop) {
if (!isset($props[$prop_id])) {
continue;
}
$definition = $props[$prop_id];
$prop_type = $definition['ui_patterns']['type_definition'];
// Normalizing attributes to an array is not working
// if the prop type is defined by type=Drupal\Core\Template\Attribute
// This should actually be done by the normalize function.
$data = $prop_type->normalize($prop);
if (isset($definition['ui_patterns']['prop_type_adapter'])) {
$prop_type_adapter_id = $definition['ui_patterns']['prop_type_adapter'];
/** @var \Drupal\ui_patterns\PropTypeAdapterInterface $prop_type_adapter */
$prop_type_adapter = $this->adaptersManager->createInstance($prop_type_adapter_id);
$data = $prop_type_adapter->transform($data);
}
$element["#props"][$prop_id] = $data;
}
return $element;
}
/** /**
* Process #attributes render property. * Process #attributes render property.
* *
......
<?php
namespace Drupal\ui_patterns\Template;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Template\ComponentNodeVisitor as CoreComponentNodeVisitor;
use Twig\Environment;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\TwigFunction;
/**
* Provides a Node Visitor to change the generated parse-tree.
*/
class ModuleNodeVisitorAfterSdc extends ModuleNodeVisitorBase {
/**
* {@inheritdoc}
*/
public function leaveNode(Node $node, Environment $env): ?Node {
if (!$node instanceof ModuleNode) {
return $node;
}
$component = $this->getComponent($node);
if (!($component instanceof Component)) {
return $node;
}
$line = $node->getTemplateLine();
$function = $this->buildPreprocessPropsFunction($line, $component, $env);
$node = $this->injectFunction($node, $function);
return $node;
}
/**
* {@inheritdoc}
*/
public function getPriority(): int {
$priority = &drupal_static(__METHOD__);
if (!isset($priority)) {
$original_node_visitor = new CoreComponentNodeVisitor($this->componentManager);
// Ensure that this node visitor's priority is higher than core's visitor,
// because this class has to run after core's class.
$priority = $original_node_visitor->getPriority() + 1;
}
return is_numeric($priority) ? (int) $priority : 0;
}
/**
* Build the _ui_patterns_preprocess_props Twig function.
*
* @param int $line
* The line.
* @param \Drupal\Core\Plugin\Component $component
* The component.
* @param \Twig\Environment $env
* A Twig Environment instance.
*
* @return \Twig\Node\Node
* The Twig function.
*/
protected function buildPreprocessPropsFunction(int $line, Component $component, Environment $env): Node {
$component_id = $component->getPluginId();
$function_parameter = new ConstantExpression($component_id, $line);
$function_parameters_node = new Node([$function_parameter]);
$function = new FunctionExpression(
new TwigFunction('_ui_patterns_preprocess_props', [$env->getExtension(TwigExtension::class), 'preprocessProps'], ['needs_context' => TRUE]),
$function_parameters_node,
$line
);
return new PrintNode($function, $line);
}
}
...@@ -4,28 +4,15 @@ namespace Drupal\ui_patterns\Template; ...@@ -4,28 +4,15 @@ namespace Drupal\ui_patterns\Template;
use Drupal\Core\Plugin\Component; use Drupal\Core\Plugin\Component;
use Drupal\Core\Render\Component\Exception\ComponentNotFoundException; use Drupal\Core\Render\Component\Exception\ComponentNotFoundException;
use Drupal\Core\Template\ComponentNodeVisitor as CoreComponentNodeVisitor;
use Drupal\Core\Theme\ComponentPluginManager; use Drupal\Core\Theme\ComponentPluginManager;
use Twig\Environment; use Twig\Environment;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\ModuleNode;
use Twig\Node\Node; use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\NodeVisitor\NodeVisitorInterface; use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TwigFunction;
/** /**
* Provides a ComponentNodeVisitor to change the generated parse-tree. * Provides a Node Visitor to change the generated parse-tree.
*
* @internal
*/ */
class ComponentNodeVisitor implements NodeVisitorInterface { abstract class ModuleNodeVisitorBase implements NodeVisitorInterface {
/**
* Node name: expr.
*/
const NODE_NAME_EXPR = 'expr';
/** /**
* The component plugin manager. * The component plugin manager.
...@@ -49,38 +36,6 @@ class ComponentNodeVisitor implements NodeVisitorInterface { ...@@ -49,38 +36,6 @@ class ComponentNodeVisitor implements NodeVisitorInterface {
return $node; return $node;
} }
/**
* {@inheritdoc}
*/
public function leaveNode(Node $node, Environment $env): ?Node {
if (!$node instanceof ModuleNode) {
return $node;
}
$component = $this->getComponent($node);
if (!($component instanceof Component)) {
return $node;
}
$line = $node->getTemplateLine();
$function = $this->buildPreprocessPropsFunction($line, $component, $env);
$node = $this->injectFunction($node, $function);
return $node;
}
/**
* {@inheritdoc}
*/
public function getPriority(): int {
$priority = &drupal_static(__METHOD__);
if (!isset($priority)) {
$original_node_visitor = new CoreComponentNodeVisitor($this->componentManager);
// Ensure that this component node visitor's priority is higher than
// core's node visitor class for components, because this class has to run
// core's class.
$priority = $original_node_visitor->getPriority() + 1;
}
return is_numeric($priority) ? (int) $priority : 0;
}
/** /**
* Finds the SDC for the current module node. * Finds the SDC for the current module node.
* *
...@@ -105,31 +60,6 @@ class ComponentNodeVisitor implements NodeVisitorInterface { ...@@ -105,31 +60,6 @@ class ComponentNodeVisitor implements NodeVisitorInterface {
} }
} }
/**
* Build the _ui_patterns_preprocess_props Twig function.
*
* @param int $line
* The line .
* @param \Drupal\Core\Plugin\Component $component
* The component.
* @param \Twig\Environment $env
* A Twig Environment instance.
*
* @return \Twig\Node\Node
* The Twig function.
*/
protected function buildPreprocessPropsFunction(int $line, Component $component, Environment $env): Node {
$component_id = $component->getPluginId();
$function_parameter = new ConstantExpression($component_id, $line);
$function_parameters_node = new Node([$function_parameter]);
$function = new FunctionExpression(
new TwigFunction('_ui_patterns_preprocess_props', [$env->getExtension(TwigExtension::class), 'preprocessProps'], ['needs_context' => TRUE]),
$function_parameters_node,
$line
);
return new PrintNode($function, $line);
}
/** /**
* Injects custom Twig nodes into given node as child nodes. * Injects custom Twig nodes into given node as child nodes.
* *
......
<?php
namespace Drupal\ui_patterns\Template;
use Drupal\Core\Plugin\Component;
use Drupal\Core\Template\ComponentNodeVisitor as CoreComponentNodeVisitor;
use Twig\Environment;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\TwigFunction;
/**
* Provides a Node Visitor to change the generated parse-tree.
*/
class ModuleNodeVisitorBeforeSdc extends ModuleNodeVisitorBase {
/**
* {@inheritdoc}
*/
public function leaveNode(Node $node, Environment $env): ?Node {
if (!$node instanceof ModuleNode) {
return $node;
}
$component = $this->getComponent($node);
if (!($component instanceof Component)) {
return $node;
}
$line = $node->getTemplateLine();
$function = $this->buildNormalizePropsFunction($line, $component, $env);
$node = $this->injectFunction($node, $function);
return $node;
}
/**
* {@inheritdoc}
*/
public function getPriority(): int {
$priority = &drupal_static(__METHOD__);
if (!isset($priority)) {
$original_node_visitor = new CoreComponentNodeVisitor($this->componentManager);
// Ensure that this node visitor's priority is lower than core's visitor,
// because this class has to run before core's class.
$priority = $original_node_visitor->getPriority() - 1;
}
return is_numeric($priority) ? (int) $priority : 0;
}
/**
* Build the _ui_patterns_preprocess_props Twig function.
*
* @param int $line
* The line.
* @param \Drupal\Core\Plugin\Component $component
* The component.
* @param \Twig\Environment $env
* A Twig Environment instance.
*
* @return \Twig\Node\Node
* The Twig function.
*/
protected function buildNormalizePropsFunction(int $line, Component $component, Environment $env): Node {
$component_id = $component->getPluginId();
$function_parameter = new ConstantExpression($component_id, $line);
$function_parameters_node = new Node([$function_parameter]);
$function = new FunctionExpression(
new TwigFunction('_ui_patterns_normalize_props', [$env->getExtension(TwigExtension::class), 'normalizeProps'], ['needs_context' => TRUE]),
$function_parameters_node,
$line
);
return new PrintNode($function, $line);
}
}
...@@ -4,7 +4,10 @@ declare(strict_types=1); ...@@ -4,7 +4,10 @@ declare(strict_types=1);
namespace Drupal\ui_patterns\Template; namespace Drupal\ui_patterns\Template;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\ui_patterns\ComponentPluginManager; use Drupal\ui_patterns\ComponentPluginManager;
use Drupal\ui_patterns\Plugin\UiPatterns\PropType\SlotPropType;
use Drupal\ui_patterns\PropTypeAdapterPluginManager;
use Twig\Extension\AbstractExtension; use Twig\Extension\AbstractExtension;
use Twig\TwigFilter; use Twig\TwigFilter;
use Twig\TwigFunction; use Twig\TwigFunction;
...@@ -23,9 +26,15 @@ class TwigExtension extends AbstractExtension { ...@@ -23,9 +26,15 @@ class TwigExtension extends AbstractExtension {
* *
* @param \Drupal\ui_patterns\ComponentPluginManager $componentManager * @param \Drupal\ui_patterns\ComponentPluginManager $componentManager
* The component plugin manager. * The component plugin manager.
* @param \Drupal\ui_patterns\PropTypeAdapterPluginManager $adapterManager
* The prop type adapter plugin manager.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
*/ */
public function __construct( public function __construct(
protected ComponentPluginManager $componentManager, protected ComponentPluginManager $componentManager,
protected PropTypeAdapterPluginManager $adapterManager,
protected MessengerInterface $messenger,
) {} ) {}
/** /**
...@@ -40,7 +49,8 @@ class TwigExtension extends AbstractExtension { ...@@ -40,7 +49,8 @@ class TwigExtension extends AbstractExtension {
*/ */
public function getNodeVisitors(): array { public function getNodeVisitors(): array {
return [ return [
new ComponentNodeVisitor($this->componentManager), new ModuleNodeVisitorBeforeSdc($this->componentManager),
new ModuleNodeVisitorAfterSdc($this->componentManager),
]; ];
} }
...@@ -49,7 +59,11 @@ class TwigExtension extends AbstractExtension { ...@@ -49,7 +59,11 @@ class TwigExtension extends AbstractExtension {
*/ */
public function getFunctions() { public function getFunctions() {
return [ return [
// @todo Remove component() before 2.0.0 release.
new TwigFunction('component', [$this, 'renderComponent']), new TwigFunction('component', [$this, 'renderComponent']),
// For ComponentNodeVisitorBeforeSdc.
new TwigFunction('_ui_patterns_normalize_props', [$this, 'normalizeProps'], ['needs_context' => TRUE]),
// For ComponentNodeVisitorAfterSdc.
new TwigFunction('_ui_patterns_preprocess_props', [$this, 'preprocessProps'], ['needs_context' => TRUE]), new TwigFunction('_ui_patterns_preprocess_props', [$this, 'preprocessProps'], ['needs_context' => TRUE]),
]; ];
} }
...@@ -80,6 +94,7 @@ class TwigExtension extends AbstractExtension { ...@@ -80,6 +94,7 @@ class TwigExtension extends AbstractExtension {
* @see \Drupal\Core\Theme\Element\ComponentElement * @see \Drupal\Core\Theme\Element\ComponentElement
*/ */
public function renderComponent(string $component_id, array $slots = [], array $props = []) { public function renderComponent(string $component_id, array $slots = [], array $props = []) {
$this->messenger->addWarning("component() Twig function is deprecated in favor of include() function and will be removed before 2.0.0.");
return [ return [
'#type' => 'component', '#type' => 'component',
'#component' => $component_id, '#component' => $component_id,
...@@ -88,6 +103,51 @@ class TwigExtension extends AbstractExtension { ...@@ -88,6 +103,51 @@ class TwigExtension extends AbstractExtension {
]; ];
} }
/**
* Normalize props (and slots).
*
* This function must not be used by the templates authors. In a perfect
* world, it would not be necessary to set such a function. We did that to be
* compatible with SDC's ComponentNodeVisitor, in order to execute props
* normalization before SDC's validate_component_props Twig function.
*
* See ModuleNodeVisitorBeforeSdc.
*
* @param array $context
* The context provided to the component.
* @param string $component_id
* The component ID.
*
* @throws \Drupal\Core\Render\Component\Exception\InvalidComponentException
*/
public function normalizeProps(array &$context, string $component_id): void {
$component = $this->componentManager->find($component_id);
$props = $component->metadata->schema['properties'] ?? [];
// Attributes prop must never be empty, to avoid the processing of SDC's
// ComponentsTwigExtension::mergeAdditionalRenderContext() which is adding
// an Attribute PHP object before running the validator.
// Attribute PHP object are casted as string by the validator and trigger
// '[attributes] String value found, but an object is required' error.
$context['attributes']['data-component-id'] = $component->getPluginId();
foreach ($context as $variable => $value) {
if (isset($component->metadata->slots[$variable])) {
$context[$variable] = SlotPropType::normalize($value);
continue;
}
if (!isset($props[$variable])) {
continue;
}
$prop_type = $props[$variable]['ui_patterns']['type_definition'];
$context[$variable] = $prop_type->normalize($value);
if (isset($props[$variable]['ui_patterns']['prop_type_adapter'])) {
$prop_type_adapter_id = $props[$variable]['ui_patterns']['prop_type_adapter'];
/** @var \Drupal\ui_patterns\PropTypeAdapterInterface $prop_type_adapter */
$prop_type_adapter = $this->adapterManager->createInstance($prop_type_adapter_id);
$context[$variable] = $prop_type_adapter->transform($context[$variable]);
}
}
}
/** /**
* Preprocess props. * Preprocess props.
* *
...@@ -96,6 +156,8 @@ class TwigExtension extends AbstractExtension { ...@@ -96,6 +156,8 @@ class TwigExtension extends AbstractExtension {
* compatible with SDC's ComponentNodeVisitor, in order to execute props * compatible with SDC's ComponentNodeVisitor, in order to execute props
* preprocessing after SDC's validate_component_props Twig function. * preprocessing after SDC's validate_component_props Twig function.
* *
* See ModuleNodeVisitorAfterSdc.
*
* @param array $context * @param array $context
* The context provided to the component. * The context provided to the component.
* @param string $component_id * @param string $component_id
......
...@@ -12,10 +12,10 @@ ...@@ -12,10 +12,10 @@
{% endif %} {% endif %}
{{ message|add_class('alert-link') }} {{ message|add_class('alert-link') }}
{% if dismissible %} {% if dismissible %}
{{ component('ui_patterns_test:close_button', {}, { {{ include('ui_patterns_test:close_button', {
attributes: create_attribute({ attributes: {
'data-bs-dismiss': 'alert' 'data-bs-dismiss': 'alert',
}), },
aria_label: 'Close'|t, aria_label: 'Close'|t,
}) }} }) }}
{% endif %} {% endif %}
......
<div class="ui-patterns-test-component"> <div{{ attributes.addClass(["ui-patterns-test-component"]) }}>
<div class="ui-patterns-props-string"> <div class="ui-patterns-props-string">
{{ string }} {{ string }}
</div> </div>
...@@ -57,13 +57,13 @@ ...@@ -57,13 +57,13 @@
{% endfor %} {% endfor %}
</div> </div>
<div class="ui-patterns-props-attributes_implicit"> <div class="ui-patterns-props-attributes_implicit">
{{ attributes_implicit }} <div{{ attributes_implicit }}></div>
</div> </div>
<div class="ui-patterns-props-attributes_ui_patterns"> <div class="ui-patterns-props-attributes_ui_patterns">
{{ attributes_ui_patterns }} <div{{ attributes_ui_patterns }}></div>
</div> </div>
<div class="ui-patterns-props-attributes_class"> <div class="ui-patterns-props-attributes_class">
{{ attributes_class }} <div{{ attributes_class }}></div>
</div> </div>
<div class="ui-patterns-slots-slot"> <div class="ui-patterns-slots-slot">
{{ slot }} {{ slot }}
......
<?php
declare(strict_types=1);
namespace Drupal\Tests\ui_patterns\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\ui_patterns\Traits\TestDataTrait;
/**
* Base class to test source plugins.
*
* @group ui_patterns
*/
class TwigVisitorTest 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 attributes.
*/
public function testAttributes() : void {
$default_context = [
'prop_string' => $this->randomMachineName(),
'attributes' => [],
];
$twig_templates = [
// Include tag.
'{% include "ui_patterns_test:test-component" with { string: prop_string } %}',
'{% include "ui_patterns_test:test-component" with { attributes: {}, string: prop_string } %}',
'{% include "ui_patterns_test:test-component" with { attributes: create_attribute(), string: prop_string } %}',
'{% include "ui_patterns_test:test-component" with { attributes: "", string: prop_string } %}',
// Embed tag.
"{% embed 'ui_patterns_test:test-component' with { attributes: {}, string: prop_string } only %}
{% block content %}
{{ content }}
{% endblock %}
{% endembed %}",
"{% embed 'ui_patterns_test:test-component' with { attributes: create_attribute(), string: prop_string } only %}
{% block content %}
{{ content }}
{% endblock %}
{% endembed %}",
"{% embed 'ui_patterns_test:test-component' with { attributes: '', string: prop_string } only %}
{% block content %}
{{ content }}
{% endblock %}
{% endembed %}",
"{% embed 'ui_patterns_test:test-component' with { string: prop_string } only %}
{% block content %}
{{ content }}
{% endblock %}
{% endembed %}",
// Include function.
'{{ include("ui_patterns_test:test-component", { string: prop_string }) }}',
'{{ include("ui_patterns_test:test-component", { attributes: {}, string: prop_string }) }}',
'{{ include("ui_patterns_test:test-component", { attributes: create_attribute(), string: prop_string }) }}',
'{{ include("ui_patterns_test:test-component", { attributes: "", string: prop_string }) }}',
];
$render_array = [
'#type' => 'inline_template',
'#context' => $default_context,
];
foreach ($twig_templates as $twig_template) {
$render_array_test = array_merge($render_array, ['#template' => $twig_template]);
$this->assertExpectedOutput(
[
"rendered_value_plain" => $default_context["prop_string"],
"rendered_value" => $default_context["prop_string"],
"assert" => "assertStringContainsString",
],
$render_array_test
);
}
}
}
...@@ -54,7 +54,7 @@ trait TestDataTrait { ...@@ -54,7 +54,7 @@ trait TestDataTrait {
* *
* @param array $expected_result * @param array $expected_result
* The expected result from the test set. * The expected result from the test set.
* @param string $result * @param mixed $result
* The result. * The result.
* @param string $message * @param string $message
* The message. * The message.
...@@ -88,17 +88,26 @@ trait TestDataTrait { ...@@ -88,17 +88,26 @@ trait TestDataTrait {
} }
if (isset($expected_result['rendered_value']) || isset($expected_result['rendered_value_plain'])) { if (isset($expected_result['rendered_value']) || isset($expected_result['rendered_value_plain'])) {
// $rendered = \Drupal::service('renderer')->renderRoot($result); // $rendered = \Drupal::service('renderer')->renderRoot($result);
$rendered = is_array($result) ? \Drupal::service('renderer')->renderRoot($result) : $result; $rendered = NULL;
try {
$rendered = is_array($result) ? \Drupal::service('renderer')->renderRoot($result) : $result;
}
catch (\Exception $e) {
// @phpstan-ignore-next-line
$this->assertTrue(FALSE, sprintf("%s: ERROR, failed to render result: %s \n (%s)", $message, $e->getMessage(), print_r($result, TRUE)));
}
if ($rendered instanceof MarkupInterface) { if ($rendered instanceof MarkupInterface) {
$rendered = "" . $rendered; $rendered = "" . $rendered;
} }
$normalized_rendered = self::normalizeMarkupString($rendered); $normalized_rendered = self::normalizeMarkupString($rendered);
if (isset($expected_result['rendered_value'])) { if (isset($expected_result['rendered_value'])) {
$this->assertContains($expected_result['rendered_value'], [$normalized_rendered], sprintf("%s: '%s' VS '%s' (%s)", $message, $expected_result['rendered_value'], $normalized_rendered, print_r($result, TRUE))); $message = sprintf("%s: '%s' VS '%s' (%s)", $message, $expected_result['rendered_value'], $normalized_rendered, print_r($result, TRUE));
$this->assertExpectedOutputGeneric($expected_result['rendered_value'], $normalized_rendered, $expected_result, $message);
} }
if (isset($expected_result['rendered_value_plain'])) { if (isset($expected_result['rendered_value_plain'])) {
$rendered_plain = Xss::filter($normalized_rendered); $rendered_plain = Xss::filter($normalized_rendered);
$this->assertContains($expected_result['rendered_value_plain'], [$rendered_plain], sprintf("%s: '%s' VS '%s'", $message, $rendered_plain, $normalized_rendered)); $message = sprintf("%s: '%s' VS '%s'", $message, $rendered_plain, $normalized_rendered);
$this->assertExpectedOutputGeneric($expected_result['rendered_value_plain'], $rendered_plain, $expected_result, $message);
} }
$assert_done = TRUE; $assert_done = TRUE;
} }
...@@ -111,6 +120,29 @@ trait TestDataTrait { ...@@ -111,6 +120,29 @@ trait TestDataTrait {
} }
} }
/**
* Assert expected output generic.
*
* @param mixed $expected_argument
* The expected argument.
* @param mixed $computed_data
* The computed data.
* @param array $expected_result_metadata
* The expected result metadata.
* @param string $message
* The message.
*/
protected function assertExpectedOutputGeneric(mixed $expected_argument, mixed $computed_data, array $expected_result_metadata, string $message = ''): void {
$haystack = $computed_data;
if (!isset($expected_result_metadata["assert"])) {
$expected_result_metadata["assert"] = "assertContains";
}
if ($expected_result_metadata["assert"] === "assertContains") {
$haystack = [$computed_data];
}
$this->{$expected_result_metadata["assert"]}($expected_argument, $haystack, $message);
}
/** /**
* Normalize a string of markup for comparison. * Normalize a string of markup for comparison.
*/ */
......
...@@ -75,6 +75,8 @@ services: ...@@ -75,6 +75,8 @@ services:
- { name: twig.extension } - { name: twig.extension }
arguments: arguments:
- "@plugin.manager.sdc" - "@plugin.manager.sdc"
- "@plugin.manager.ui_patterns_prop_type_adapter"
- '@messenger'
ui_patterns.sample_entity_generator: ui_patterns.sample_entity_generator:
class: Drupal\ui_patterns\Entity\SampleEntityGenerator class: Drupal\ui_patterns\Entity\SampleEntityGenerator
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment