Skip to content

Commit f9892d9

Browse files
committed
feat(live): add test helpers
1 parent e7fc5b8 commit f9892d9

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Test;
13+
14+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\UX\TwigComponent\ComponentFactory;
16+
17+
/**
18+
* @author Kevin Bond <[email protected]>
19+
*/
20+
trait InteractsWithLiveComponents
21+
{
22+
protected function createLiveComponent(string $name, array $data = []): TestLiveComponent
23+
{
24+
if (!$this instanceof KernelTestCase) {
25+
throw new \LogicException(sprintf('The "%s" trait can only be used on "%s" classes.', __TRAIT__, KernelTestCase::class));
26+
}
27+
28+
/** @var ComponentFactory $factory */
29+
$factory = self::getContainer()->get('ux.twig_component.component_factory');
30+
$metadata = $factory->metadataFor($name);
31+
32+
if (!$metadata->get('live')) {
33+
throw new \LogicException(sprintf('The "%s" component is not a live component.', $name));
34+
}
35+
36+
return new TestLiveComponent(
37+
$metadata,
38+
$data,
39+
$factory,
40+
self::getContainer()->get('test.client'),
41+
self::getContainer()->get('ux.live_component.component_hydrator'),
42+
self::getContainer()->get('ux.live_component.metadata_factory'),
43+
self::getContainer()->get('router'),
44+
);
45+
}
46+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\LiveComponent\Test;
13+
14+
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
15+
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
16+
use Symfony\UX\LiveComponent\LiveComponentHydrator;
17+
use Symfony\UX\LiveComponent\Metadata\LiveComponentMetadataFactory;
18+
use Symfony\UX\TwigComponent\ComponentFactory;
19+
use Symfony\UX\TwigComponent\ComponentMetadata;
20+
use Symfony\UX\TwigComponent\MountedComponent;
21+
22+
/**
23+
* @author Kevin Bond <[email protected]>
24+
*/
25+
final class TestLiveComponent
26+
{
27+
private string $rendered;
28+
private array $props;
29+
private string $csrfToken;
30+
private object $component;
31+
32+
/**
33+
* @internal
34+
*/
35+
public function __construct(
36+
private ComponentMetadata $metadata,
37+
array $data,
38+
private ComponentFactory $factory,
39+
private KernelBrowser $client,
40+
private LiveComponentHydrator $hydrator,
41+
private LiveComponentMetadataFactory $metadataFactory,
42+
private UrlGeneratorInterface $router,
43+
) {
44+
$this->client->catchExceptions(false);
45+
46+
$mounted = $this->factory->create($this->metadata->getName(), $data);
47+
$props = $this->hydrator->dehydrate(
48+
$mounted->getComponent(),
49+
$mounted->getAttributes(),
50+
$this->metadataFactory->getMetadata($mounted->getName())
51+
);
52+
53+
$this->client->request('GET', $this->router->generate(
54+
$this->metadata->get('route'),
55+
[
56+
'_live_component' => $this->metadata->getName(),
57+
'props' => json_encode($props->getProps()),
58+
]
59+
));
60+
61+
$this->updateState();
62+
}
63+
64+
public function render(): string // todo return RenderedComponent
65+
{
66+
return $this->rendered;
67+
}
68+
69+
public function component(): object
70+
{
71+
if (isset($this->component)) {
72+
return $this->component;
73+
}
74+
75+
$component = $this->factory->get($this->metadata->getName());
76+
$componentAttributes = $this->hydrator->hydrate(
77+
$component,
78+
$this->props,
79+
[],
80+
$this->metadataFactory->getMetadata($this->metadata->getName()),
81+
);
82+
83+
return $this->component = (new MountedComponent($this->metadata->getName(), $component, $componentAttributes))->getComponent();
84+
}
85+
86+
/**
87+
* @param array<string,mixed> $arguments
88+
*/
89+
public function call(string $action, array $arguments = []): self
90+
{
91+
return $this->request(['args' => $arguments], $action);
92+
}
93+
94+
/**
95+
* @param array<string,mixed> $arguments
96+
*/
97+
public function emit(string $event, array $arguments = []): self
98+
{
99+
return $this->call($event, $arguments);
100+
}
101+
102+
public function set(string $prop, mixed $value): self
103+
{
104+
return $this->request(['updated' => [$prop => $value]]);
105+
}
106+
107+
private function request(array $content = [], ?string $action = null): self
108+
{
109+
$this->client->request(
110+
'POST',
111+
$this->router->generate(
112+
$this->metadata->get('route'),
113+
array_filter([
114+
'_live_component' => $this->metadata->getName(),
115+
'_live_action' => $action,
116+
])
117+
),
118+
server: ['HTTP_X_CSRF_TOKEN' => $this->csrfToken],
119+
content: json_encode(array_merge($content, ['props' => $this->props])),
120+
);
121+
122+
return $this->updateState();
123+
}
124+
125+
private function updateState(): self
126+
{
127+
$crawler = $this->client->getCrawler();
128+
129+
$this->props = json_decode($crawler->filter('[data-live-props-value]')->attr('data-live-props-value'), true, flags: \JSON_THROW_ON_ERROR);
130+
$this->csrfToken = $crawler->filter('[data-live-csrf-value]')->attr('data-live-csrf-value');
131+
$this->rendered = $this->client->getResponse()->getContent();
132+
133+
unset($this->component);
134+
135+
return $this;
136+
}
137+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
3+
namespace Symfony\UX\LiveComponent\Tests\Functional\Test;
4+
5+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
6+
use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
7+
8+
/**
9+
* @author Kevin Bond <[email protected]>
10+
*/
11+
final class InteractsWithLiveComponentsTest extends KernelTestCase
12+
{
13+
use InteractsWithLiveComponents;
14+
15+
public function testCanRenderInitialData(): void
16+
{
17+
$testComponent = $this->createLiveComponent('component2');
18+
19+
$this->assertStringContainsString('Count: 1', $testComponent->render());
20+
$this->assertSame(1, $testComponent->component()->count);
21+
}
22+
23+
public function testCanCallLiveAction(): void
24+
{
25+
$testComponent = $this->createLiveComponent('component2');
26+
27+
$this->assertStringContainsString('Count: 1', $testComponent->render());
28+
$this->assertSame(1, $testComponent->component()->count);
29+
30+
$testComponent->call('increase');
31+
32+
$this->assertStringContainsString('Count: 2', $testComponent->render());
33+
$this->assertSame(2, $testComponent->component()->count);
34+
}
35+
36+
public function testCanCallLiveActionWithArguments(): void
37+
{
38+
$testComponent = $this->createLiveComponent('component6');
39+
40+
$this->assertStringContainsString('Arg1: not provided', $testComponent->render());
41+
$this->assertStringContainsString('Arg2: not provided', $testComponent->render());
42+
$this->assertStringContainsString('Arg3: not provided', $testComponent->render());
43+
$this->assertNull($testComponent->component()->arg1);
44+
$this->assertNull($testComponent->component()->arg2);
45+
$this->assertNull($testComponent->component()->arg3);
46+
47+
$testComponent->call('inject', ['arg1' => 'hello', 'arg2' => 666, 'custom' => '33.3']);
48+
49+
$this->assertStringContainsString('Arg1: hello', $testComponent->render());
50+
$this->assertStringContainsString('Arg2: 666', $testComponent->render());
51+
$this->assertStringContainsString('Arg3: 33.3', $testComponent->render());
52+
$this->assertSame('hello', $testComponent->component()->arg1);
53+
$this->assertSame(666, $testComponent->component()->arg2);
54+
$this->assertSame(33.3, $testComponent->component()->arg3);
55+
}
56+
57+
public function testCanSetLiveProp(): void
58+
{
59+
$testComponent = $this->createLiveComponent('component_with_writable_props');
60+
61+
$this->assertStringContainsString('Count: 1', $testComponent->render());
62+
$this->assertSame(1, $testComponent->component()->count);
63+
64+
$testComponent->set('count', 100);
65+
66+
$this->assertStringContainsString('Count: 100', $testComponent->render());
67+
$this->assertSame(100, $testComponent->component()->count);
68+
}
69+
}

0 commit comments

Comments
 (0)