ux icon indicating copy to clipboard operation
ux copied to clipboard

[TwigComponent] Blocks set with TestHelper don't get evaluated

Open janopae opened this issue 3 weeks ago • 6 comments

When playing with the reproducer I created for https://github.com/symfony/ux/issues/3210, I realised InteractsWithTwigComponents::renderTwigComponent did not at all behave the way I expected:

Twig code inside blocks set using the $blocks parameter won't be evaluated. Variables won't be resolved, and Twig functions won't be executed. Instead, the resulting HTML will contain placeholders like {{ message }}.

This means that at the current state of the helper, basic interactions of components with their blocks can't be tested: You can't test if variables are being passed correctly or if their content is right.

This is a feature request to allow some form of testing the variables Twig components pass to their blocks.

https://github.com/symfony/ux/pull/3211 would be a way to implement this, as it would enable evaluation of the blocks.

janopae avatar Dec 05 '25 09:12 janopae

I'm thinking you're reading this in the wrong side.

InteractWithTwigComponent is not made to test what context your component passes to Twig... but in the contrary to test what HTML is rendered depending on the context and what data is passed. ... and was mostly documented for embeds only, as passing blocks to TwigComponent is very similar to the behaviour of embed blocks.

Passing determinist/dependency-free content (instead of runtime resolved blocks) can help tests performances while increasing their value.

basic interactions of components with their blocks can't be tested

Not sure what you mean here, how would you do in Twig?

smnandre avatar Dec 05 '25 23:12 smnandre

Imagine a list component:

<ul>
  {% for item in items %}
    <li>{% block content %}{# Variable `item` shall be available in the block and contain the current item #}{% endblock %}</li>
  {% endfor %}
</ul>

It will be used like this:

<twig:App:List items="{{ items }}">
  <h4>{{ item.title }}</h4>
  <p>{{ item.text }}</p>
</twig:App:List>

How would you test that the variable {{ item }} is available in the block and contains the right content?

Given the structure of the component, that would be the main thing to test IMHO, because it's a significant part of the contract between the component and its users.

janopae avatar Dec 08 '25 12:12 janopae

Imagine a list component:

 {% for item in items %}
   <li>{% block content %}{# Variable `item` shall be available in the block and contain the current item #}{% endblock %}</> li>
 {% endfor %}
</ul>
> (...) 
>How would you test that the variable {{ item }} is available in the block and contains the right content?

You should not test the framework, but your code. 

But if you want to ... make a functional test ? 

Two templates, one render, and test the end result? 

smnandre avatar Dec 08 '25 17:12 smnandre

You should not test the framework, but your code.

So how do I test that my code sets those vairables correctly without testing the framework? Without my code, no framework will set an item variable. And especially will no framework set the far more complicated variables real-life templates will set (rather than my simplified example).

For example, one component I currently want to test groups a list of date intervals by day, it has a block for each day, and will pass the following vars to the block: a list of intervals for that day, the date, and a boolean whether its the last day in the list. Doesn't that deserve some testing?

I test my code to ensure the interfaces my code provides work and are stable – in this case, it's the interface my template code creates by declaring that block. Ideally, I'd like to test all interfaces without including more of the framework than I need, and ideally with as little as possible artificial code only written for testing purposes. Ideally, I'd have all code specific for the test case together in one function.

Wouldn't it be the perfect responsibility for a test helper in a framework to allow all kinds of interfaces the framework enables you to define to be tested? Especially, if the test helper that already exists needs so little adjustment to support this, and the downsides are so small and so easy to work around?[^1]

[^1]: As written, https://github.com/symfony/ux/pull/3211 will only bypass the cache if the content of the block you pass changes. If you need to change your test block content frequently, you could still do the trick the test helper would do internally before: Just pass your changing HTML as a var and set consistent block code that prints that var as |raw.

janopae avatar Dec 08 '25 21:12 janopae

The documentation states

You can test how your component is mounted and rendered using the InteractsWithTwigComponents trait:

A component's contract is that, when called, it returns a string.

That's what the trait is designed to test, and it works well for many users.

If you want to propose something else, it should be additive and must not change existing behavior.

smnandre avatar Dec 14 '25 06:12 smnandre

I think you're 100 % right. I hadn't read https://github.com/symfony/ux/pull/3211#discussion_r2594245397 yet when I wrote my last comment here. We should solve this without the risk to break existing test code.

janopae avatar Dec 16 '25 09:12 janopae