-
-
Notifications
You must be signed in to change notification settings - Fork 364
[Twig] add test helper #821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\UX\TwigComponent\Test; | ||
|
||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||
|
||
/** | ||
* @author Kevin Bond <kevinbond@gmail.com> | ||
*/ | ||
trait InteractsWithTwigComponents | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am just wondering why do we use trait here? In my use case, I gonna create a dedicated test class for my component, and I not gonna mixed my components tests with other tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is just a pattern I've found useful with my other testing libraries. I like using traits to avoid forcing a single type of test. class MyComponentTest extends KernelTestCase
{
use InteractsWithTwigComponents, ResetDatabase, Factories;
// ...
} I get the above would still work if we made this trait into and abstract There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ho yes using this trait with foundry just rocks! Thanks for all of this 😁 |
||
{ | ||
protected function mountTwigComponent(string $name, array $data = []): object | ||
{ | ||
if (!$this instanceof KernelTestCase) { | ||
throw new \LogicException(sprintf('The "%s" trait can only be used on "%s" classes.', __TRAIT__, KernelTestCase::class)); | ||
} | ||
|
||
return static::getContainer()->get('ux.twig_component.component_factory')->create($name, $data)->getComponent(); | ||
} | ||
|
||
/** | ||
* @param array<string,string> $blocks | ||
*/ | ||
protected function renderTwigComponent(string $name, array $data = [], ?string $content = null, array $blocks = []): RenderedComponent | ||
{ | ||
if (!$this instanceof KernelTestCase) { | ||
throw new \LogicException(sprintf('The "%s" trait can only be used on "%s" classes.', __TRAIT__, KernelTestCase::class)); | ||
} | ||
|
||
$blocks = array_filter(array_merge($blocks, ['content' => $content])); | ||
|
||
if (!$blocks) { | ||
return new RenderedComponent(self::getContainer()->get('twig') | ||
->createTemplate('{{ component(name, data) }}') | ||
->render([ | ||
'name' => $name, | ||
'data' => $data, | ||
]) | ||
); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is pretty nuts 👍 |
||
} | ||
|
||
$template = sprintf('{%% component "%s" with data %%}', addslashes($name)); | ||
|
||
foreach (array_keys($blocks) as $blockName) { | ||
$template .= sprintf('{%% block %1$s %%}{{ blocks.%1$s|raw }}{%% endblock %%}', $blockName); | ||
} | ||
|
||
$template .= '{% endcomponent %}'; | ||
|
||
return new RenderedComponent(self::getContainer()->get('twig') | ||
->createTemplate($template) | ||
->render([ | ||
'data' => $data, | ||
'blocks' => $blocks, | ||
]) | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\UX\TwigComponent\Test; | ||
|
||
/** | ||
* @author Kevin Bond <kevinbond@gmail.com> | ||
*/ | ||
final class RenderedComponent implements \Stringable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this stringable object to wrap the rendered html so we can possibly add something like described in #818 (comment) in the future. I wasn't sure if changing the type-hint from string -> stringable object as the return type of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's start with this - easier to remove this later if we end up not needing it or it's a pain for some reason than to go in the other direction. |
||
{ | ||
/** | ||
* @internal | ||
*/ | ||
public function __construct(private string $html) | ||
{ | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return $this->html; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\UX\TwigComponent\Tests\Fixtures\Component; | ||
|
||
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent; | ||
|
||
/** | ||
* @author Kevin Bond <kevinbond@gmail.com> | ||
*/ | ||
#[AsTwigComponent] | ||
final class WithSlots | ||
{ | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<div> | ||
{% block content %}{% endblock %} | ||
{% block slot1 %}{% endblock %} | ||
{% block slot2 %}{% endblock %} | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php | ||
|
||
/* | ||
* This file is part of the Symfony package. | ||
* | ||
* (c) Fabien Potencier <fabien@symfony.com> | ||
* | ||
* For the full copyright and license information, please view the LICENSE | ||
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\UX\TwigComponent\Tests\Integration\Test; | ||
|
||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; | ||
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents; | ||
use Symfony\UX\TwigComponent\Tests\Fixtures\Component\ComponentA; | ||
use Symfony\UX\TwigComponent\Tests\Fixtures\Component\WithSlots; | ||
use Symfony\UX\TwigComponent\Tests\Fixtures\Service\ServiceA; | ||
|
||
final class InteractsWithTwigComponentsTest extends KernelTestCase | ||
{ | ||
use InteractsWithTwigComponents; | ||
|
||
/** | ||
* @dataProvider componentANameProvider | ||
*/ | ||
public function testCanMountComponent(string $name): void | ||
{ | ||
$component = $this->mountTwigComponent($name, [ | ||
'propA' => 'prop a value', | ||
'propB' => 'prop b value', | ||
]); | ||
|
||
$this->assertInstanceof(ComponentA::class, $component); | ||
$this->assertInstanceOf(ServiceA::class, $component->getService()); | ||
$this->assertSame('prop a value', $component->propA); | ||
$this->assertSame('prop b value', $component->getPropB()); | ||
} | ||
|
||
/** | ||
* @dataProvider componentANameProvider | ||
*/ | ||
public function testCanRenderComponent(string $name): void | ||
{ | ||
$rendered = $this->renderTwigComponent($name, [ | ||
'propA' => 'prop a value', | ||
'propB' => 'prop b value', | ||
]); | ||
|
||
$this->assertStringContainsString('propA: prop a value', $rendered); | ||
$this->assertStringContainsString('propB: prop b value', $rendered); | ||
$this->assertStringContainsString('service: service a value', $rendered); | ||
} | ||
|
||
/** | ||
* @dataProvider withSlotsNameProvider | ||
*/ | ||
public function testCanRenderComponentWithSlots(string $name): void | ||
{ | ||
$rendered = $this->renderTwigComponent( | ||
name: $name, | ||
content: '<p>some content</p>', | ||
blocks: [ | ||
'slot1' => '<p>some slot1 content</p>', | ||
'slot2' => $this->renderTwigComponent('component_a', [ | ||
'propA' => 'prop a value', | ||
'propB' => 'prop b value', | ||
]), | ||
], | ||
); | ||
|
||
$this->assertStringContainsString('<p>some content</p>', $rendered); | ||
$this->assertStringContainsString('<p>some slot1 content</p>', $rendered); | ||
$this->assertStringContainsString('propA: prop a value', $rendered); | ||
$this->assertStringContainsString('propB: prop b value', $rendered); | ||
$this->assertStringContainsString('service: service a value', $rendered); | ||
} | ||
|
||
public static function componentANameProvider(): iterable | ||
{ | ||
yield ['component_a']; | ||
yield [ComponentA::class]; | ||
} | ||
|
||
public static function withSlotsNameProvider(): iterable | ||
{ | ||
yield ['WithSlots']; | ||
yield [WithSlots::class]; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is my preferred name (:wink:) but I have no problem adding the Trait suffix or naming something else that jives with Symfony standards.