Skip to content

Commit

Permalink
feat(twig): add test helper
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Apr 28, 2023
1 parent e7fc5b8 commit ce5f2db
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 2 deletions.
55 changes: 55 additions & 0 deletions src/TwigComponent/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,61 @@ And in your component template you can access your embedded block
{% block footer %}{% endblock %}
</div>

Test Helpers
------------

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

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\UX\TwigComponent\Test\InteractsWithTwigComponents;

class MyComponentTest extends KernelTestCase
{
use InteractsWithTwigComponents;

public function testComponentMount(): void
{
$component = $this->mountTwigComponent(
name: 'MyComponent',
data: ['foo' => 'bar'],
);

$this->assertInstanceOf(MyComponent::class, $component);
$this->assertSame('bar', $component->foo);
}

public function testComponentRenders(): void
{
$rendered = $this->renderTwigComponent(
name: 'MyComponent',
data: ['foo' => 'bar'],
);

$this->assertStringContainsString('bar', $rendered);
}

public function testEmbeddedComponentRenders(): void
{
$rendered = $this->renderTwigComponent(
name: 'MyComponent',
data: ['foo' => 'bar'],
content: '<div>My content</div>', // "content" (default) block
blocks: [
'header' => '<div>My header</div>',
'menu' => $this->renderTwigComponent('Menu'), // can embed other components
],
);

$this->assertStringContainsString('bar', $rendered);
}
}

.. note::

The ``InteractsWithTwigComponents`` trait can only be used in tests that extend
``Symfony\Bundle\FrameworkBundle\Test\KernelTestCase``.

Contributing
------------

Expand Down
12 changes: 10 additions & 2 deletions src/TwigComponent/src/ComponentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function __construct(
public function metadataFor(string $name): ComponentMetadata
{
if (!$config = $this->config[$name] ?? null) {
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config))));
$this->throwUnknownComponentException($name);
}

return new ComponentMetadata($config);
Expand Down Expand Up @@ -143,7 +143,7 @@ private function mount(object $component, array &$data): void
private function getComponent(string $name): object
{
if (!$this->components->has($name)) {
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->components->getProvidedServices()))));
$this->throwUnknownComponentException($name);
}

return $this->components->get($name);
Expand Down Expand Up @@ -182,4 +182,12 @@ private function postMount(object $component, array $data): array

return $data;
}

/**
* @return never
*/
private function throwUnknownComponentException(string $name): void
{
throw new \InvalidArgumentException(sprintf('Unknown component "%s". The registered components are: %s', $name, implode(', ', array_keys($this->config))));
}
}
67 changes: 67 additions & 0 deletions src/TwigComponent/src/Test/InteractsWithTwigComponents.php
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 <[email protected]>
*
* 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 <[email protected]>
*/
trait InteractsWithTwigComponents
{
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,
])
);
}

$template = sprintf('{%% component %s with data %%}', $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,
])
);
}
}
27 changes: 27 additions & 0 deletions src/TwigComponent/src/Test/RenderedComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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 <[email protected]>
*/
final class RenderedComponent implements \Stringable
{
public function __construct(private string $html)
{
}

public function __toString(): string
{
return $this->html;
}
}
13 changes: 13 additions & 0 deletions src/TwigComponent/tests/Fixtures/Component/WithSlots.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Symfony\UX\TwigComponent\Tests\Fixtures\Component;

use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;

/**
* @author Kevin Bond <[email protected]>
*/
#[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,68 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* 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\Service\ServiceA;

final class InteractsWithTwigComponentsTest extends KernelTestCase
{
use InteractsWithTwigComponents;

public function testCanMountComponent(): void
{
$component = $this->mountTwigComponent('component_a', [
'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());
}

public function testCanRenderComponent(): void
{
$rendered = $this->renderTwigComponent('component_a', [
'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);
}

public function testCanRenderComponentWithSlots(): void
{
$rendered = $this->renderTwigComponent(
name: 'WithSlots',
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);
}
}

0 comments on commit ce5f2db

Please sign in to comment.