Skip to content

Commit

Permalink
feat(live): add test helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
kbond committed Aug 16, 2023
1 parent b2c00e6 commit 4b06ebd
Show file tree
Hide file tree
Showing 4 changed files with 266 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/LiveComponent/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## 2.11.0

- Add helper for testing live components.

## 2.9.0

- Add support for symfony/asset-mapper
Expand Down
46 changes: 46 additions & 0 deletions src/LiveComponent/src/Test/InteractsWithLiveComponents.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?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\LiveComponent\Test;

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

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
trait InteractsWithLiveComponents
{
protected function createLiveComponent(string $name, array $data = []): TestLiveComponent
{
if (!$this instanceof KernelTestCase) {
throw new \LogicException(sprintf('The "%s" trait can only be used on "%s" classes.', __TRAIT__, KernelTestCase::class));
}

/** @var ComponentFactory $factory */
$factory = self::getContainer()->get('ux.twig_component.component_factory');
$metadata = $factory->metadataFor($name);

if (!$metadata->get('live')) {
throw new \LogicException(sprintf('The "%s" component is not a live component.', $name));
}

return new TestLiveComponent(
$metadata,
$data,
$factory,
self::getContainer()->get('test.client'),
self::getContainer()->get('ux.live_component.component_hydrator'),
self::getContainer()->get('ux.live_component.metadata_factory'),
self::getContainer()->get('router'),
);
}
}
138 changes: 138 additions & 0 deletions src/LiveComponent/src/Test/TestLiveComponent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
<?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\LiveComponent\Test;

use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\UX\LiveComponent\LiveComponentHydrator;
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentMetadata;
use Symfony\UX\TwigComponent\MountedComponent;
use Symfony\UX\TwigComponent\Test\RenderedComponent;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class TestLiveComponent
{
private string $rendered;
private array $props;
private string $csrfToken;
private object $component;

/**
* @internal
*/
public function __construct(
private ComponentMetadata $metadata,
array $data,
private ComponentFactory $factory,
private KernelBrowser $client,
private LiveComponentHydrator $hydrator,
private LiveComponentMetadataFactory $metadataFactory,
private UrlGeneratorInterface $router,
) {
$this->client->catchExceptions(false);

$mounted = $this->factory->create($this->metadata->getName(), $data);
$props = $this->hydrator->dehydrate(
$mounted->getComponent(),
$mounted->getAttributes(),
$this->metadataFactory->getMetadata($mounted->getName())
);

$this->client->request('GET', $this->router->generate(
$this->metadata->get('route'),
[
'_live_component' => $this->metadata->getName(),
'props' => json_encode($props->getProps()),
]
));

$this->updateState();
}

public function render(): RenderedComponent
{
return new RenderedComponent($this->rendered);
}

public function component(): object
{
if (isset($this->component)) {
return $this->component;
}

$component = $this->factory->get($this->metadata->getName());
$componentAttributes = $this->hydrator->hydrate(
$component,
$this->props,
[],
$this->metadataFactory->getMetadata($this->metadata->getName()),
);

return $this->component = (new MountedComponent($this->metadata->getName(), $component, $componentAttributes))->getComponent();
}

/**
* @param array<string,mixed> $arguments
*/
public function call(string $action, array $arguments = []): self
{
return $this->request(['args' => $arguments], $action);
}

/**
* @param array<string,mixed> $arguments
*/
public function emit(string $event, array $arguments = []): self
{
return $this->call($event, $arguments);
}

public function set(string $prop, mixed $value): self
{
return $this->request(['updated' => [$prop => $value]]);
}

private function request(array $content = [], ?string $action = null): self
{
$this->client->request(
'POST',
$this->router->generate(
$this->metadata->get('route'),
array_filter([
'_live_component' => $this->metadata->getName(),
'_live_action' => $action,
])
),
parameters: ['data' => json_encode(array_merge($content, ['props' => $this->props]))],
server: ['HTTP_X_CSRF_TOKEN' => $this->csrfToken],
);

return $this->updateState();
}

private function updateState(): self
{
$crawler = $this->client->getCrawler();

$this->props = json_decode($crawler->filter('[data-live-props-value]')->attr('data-live-props-value'), true, flags: \JSON_THROW_ON_ERROR);
$this->csrfToken = $crawler->filter('[data-live-csrf-value]')->attr('data-live-csrf-value');
$this->rendered = $this->client->getResponse()->getContent();

unset($this->component);

return $this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

namespace Symfony\UX\LiveComponent\Tests\Functional\Test;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;

/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
final class InteractsWithLiveComponentsTest extends KernelTestCase
{
use InteractsWithLiveComponents;

public function testCanRenderInitialData(): void
{
$testComponent = $this->createLiveComponent('component2');

$this->assertStringContainsString('Count: 1', (string) $testComponent->render());
$this->assertSame(1, $testComponent->component()->count);
}

public function testCanCreateWithClassString(): void
{
$testComponent = $this->createLiveComponent(Component2::class);

$this->assertStringContainsString('Count: 1', (string) $testComponent->render());
$this->assertSame(1, $testComponent->component()->count);
}

public function testCanCallLiveAction(): void
{
$testComponent = $this->createLiveComponent('component2');

$this->assertStringContainsString('Count: 1', $testComponent->render());
$this->assertSame(1, $testComponent->component()->count);

$testComponent->call('increase');

$this->assertStringContainsString('Count: 2', $testComponent->render());
$this->assertSame(2, $testComponent->component()->count);
}

public function testCanCallLiveActionWithArguments(): void
{
$testComponent = $this->createLiveComponent('component6');

$this->assertStringContainsString('Arg1: not provided', $testComponent->render());
$this->assertStringContainsString('Arg2: not provided', $testComponent->render());
$this->assertStringContainsString('Arg3: not provided', $testComponent->render());
$this->assertNull($testComponent->component()->arg1);
$this->assertNull($testComponent->component()->arg2);
$this->assertNull($testComponent->component()->arg3);

$testComponent->call('inject', ['arg1' => 'hello', 'arg2' => 666, 'custom' => '33.3']);

$this->assertStringContainsString('Arg1: hello', $testComponent->render());
$this->assertStringContainsString('Arg2: 666', $testComponent->render());
$this->assertStringContainsString('Arg3: 33.3', $testComponent->render());
$this->assertSame('hello', $testComponent->component()->arg1);
$this->assertSame(666, $testComponent->component()->arg2);
$this->assertSame(33.3, $testComponent->component()->arg3);
}

public function testCanSetLiveProp(): void
{
$testComponent = $this->createLiveComponent('component_with_writable_props');

$this->assertStringContainsString('Count: 1', $testComponent->render());
$this->assertSame(1, $testComponent->component()->count);

$testComponent->set('count', 100);

$this->assertStringContainsString('Count: 100', $testComponent->render());
$this->assertSame(100, $testComponent->component()->count);
}
}

0 comments on commit 4b06ebd

Please sign in to comment.