Skip to content

Commit d0a4713

Browse files
kbondweaverryan
authored andcommitted
feat(live): improve TestLiveComponent::actingAs()
1 parent aec04ad commit d0a4713

File tree

4 files changed

+90
-36
lines changed

4 files changed

+90
-36
lines changed

src/LiveComponent/src/Test/TestLiveComponent.php

+47-34
Original file line numberDiff line numberDiff line change
@@ -28,48 +28,22 @@
2828
*/
2929
final class TestLiveComponent
3030
{
31+
private bool $performedInitialRequest = false;
32+
3133
/**
3234
* @internal
3335
*/
3436
public function __construct(
3537
private ComponentMetadata $metadata,
36-
array $data,
38+
private array $data,
3739
private ComponentFactory $factory,
3840
private KernelBrowser $client,
3941
private LiveComponentHydrator $hydrator,
4042
private LiveComponentMetadataFactory $metadataFactory,
4143
private UrlGeneratorInterface $router,
4244
) {
4345
$this->client->catchExceptions(false);
44-
45-
$data['attributes']['id'] ??= 'in-a-real-scenario-it-would-already-have-one---provide-one-yourself-if-needed';
46-
47-
$mounted = $this->factory->create($this->metadata->getName(), $data);
48-
$props = $this->hydrator->dehydrate(
49-
$mounted->getComponent(),
50-
$mounted->getAttributes(),
51-
$this->metadataFactory->getMetadata($mounted->getName())
52-
);
53-
54-
if ('POST' === strtoupper($this->metadata->get('method'))) {
55-
$this->client->request(
56-
'POST',
57-
$this->router->generate($this->metadata->get('route'), [
58-
'_live_component' => $this->metadata->getName(),
59-
]),
60-
[
61-
'data' => json_encode(['props' => $props->getProps()], flags: \JSON_THROW_ON_ERROR),
62-
],
63-
);
64-
} else {
65-
$this->client->request('GET', $this->router->generate(
66-
$this->metadata->get('route'),
67-
[
68-
'_live_component' => $this->metadata->getName(),
69-
'props' => json_encode($props->getProps(), flags: \JSON_THROW_ON_ERROR),
70-
]
71-
));
72-
}
46+
$this->data['attributes']['data-live-id'] ??= 'in-a-real-scenario-it-would-already-have-one---provide-one-yourself-if-needed';
7347
}
7448

7549
public function render(): RenderedComponent
@@ -95,6 +69,7 @@ public function component(): object
9569
*/
9670
public function actingAs(object $user, string $firewallContext = 'main'): self
9771
{
72+
// we call loginUser() on the raw client in case the entire component requires authentication
9873
$this->client->loginUser($user, $firewallContext);
9974

10075
return $this;
@@ -145,14 +120,14 @@ public function refresh(): self
145120

146121
public function response(): Response
147122
{
148-
return $this->client->getResponse();
123+
return $this->client()->getResponse();
149124
}
150125

151126
private function request(array $content = [], ?string $action = null): self
152127
{
153128
$csrfToken = $this->csrfToken();
154129

155-
$this->client->request(
130+
$this->client()->request(
156131
'POST',
157132
$this->router->generate(
158133
$this->metadata->get('route'),
@@ -170,7 +145,7 @@ private function request(array $content = [], ?string $action = null): self
170145

171146
private function props(): array
172147
{
173-
$crawler = $this->client->getCrawler();
148+
$crawler = $this->client()->getCrawler();
174149

175150
if (!\count($node = $crawler->filter('[data-live-props-value]'))) {
176151
throw new \LogicException('A live component action has redirected and you can no longer access the component.');
@@ -181,12 +156,50 @@ private function props(): array
181156

182157
private function csrfToken(): ?string
183158
{
184-
$crawler = $this->client->getCrawler();
159+
$crawler = $this->client()->getCrawler();
185160

186161
if (!\count($node = $crawler->filter('[data-live-csrf-value]'))) {
187162
return null;
188163
}
189164

190165
return $node->attr('data-live-csrf-value');
191166
}
167+
168+
private function client(): KernelBrowser
169+
{
170+
if ($this->performedInitialRequest) {
171+
return $this->client;
172+
}
173+
174+
$mounted = $this->factory->create($this->metadata->getName(), $this->data);
175+
$props = $this->hydrator->dehydrate(
176+
$mounted->getComponent(),
177+
$mounted->getAttributes(),
178+
$this->metadataFactory->getMetadata($mounted->getName())
179+
);
180+
181+
if ('POST' === strtoupper($this->metadata->get('method'))) {
182+
$this->client->request(
183+
'POST',
184+
$this->router->generate($this->metadata->get('route'), [
185+
'_live_component' => $this->metadata->getName(),
186+
]),
187+
[
188+
'data' => json_encode(['props' => $props->getProps()], flags: \JSON_THROW_ON_ERROR),
189+
],
190+
);
191+
} else {
192+
$this->client->request('GET', $this->router->generate(
193+
$this->metadata->get('route'),
194+
[
195+
'_live_component' => $this->metadata->getName(),
196+
'props' => json_encode($props->getProps(), flags: \JSON_THROW_ON_ERROR),
197+
]
198+
));
199+
}
200+
201+
$this->performedInitialRequest = true;
202+
203+
return $this->client;
204+
}
192205
}

src/LiveComponent/tests/Fixtures/Kernel.php

+9-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Psr\Log\NullLogger;
1717
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
1818
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
19+
use Symfony\Bundle\SecurityBundle\Security;
1920
use Symfony\Bundle\SecurityBundle\SecurityBundle;
2021
use Symfony\Bundle\TwigBundle\TwigBundle;
2122
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -118,13 +119,19 @@ protected function configureContainer(ContainerConfigurator $c): void
118119
'default_path' => '%kernel.project_dir%/tests/Fixtures/templates',
119120
]);
120121

121-
$c->extension('security', [
122+
$security = [
122123
'password_hashers' => [InMemoryUser::class => 'plaintext'],
123124
'providers' => ['users' => ['memory' => ['users' => ['kevin' => ['password' => 'pass', 'roles' => ['ROLE_USER']]]]]],
124125
'firewalls' => ['main' => [
125126
'lazy' => true,
126127
]],
127-
]);
128+
];
129+
130+
if (!class_exists(Security::class)) {
131+
$security['enable_authenticator_manager'] = true;
132+
}
133+
134+
$c->extension('security', $security);
128135

129136
$c->extension('twig_component', [
130137
'defaults' => [

src/LiveComponent/tests/Functional/EventListener/LiveComponentSubscriberTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\DomCrawler\Crawler;
1616
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1717
use Symfony\Component\Security\Core\User\InMemoryUser;
18+
use Symfony\Component\Security\Http\Attribute\IsGranted;
1819
use Symfony\UX\LiveComponent\Tests\Fixtures\Entity\Entity1;
1920
use Symfony\UX\LiveComponent\Tests\LiveComponentTestHelper;
2021
use Zenstruck\Browser\Test\HasBrowser;
@@ -504,6 +505,10 @@ public function testWithNullableEntity(): void
504505

505506
public function testCanHaveControllerAttributes(): void
506507
{
508+
if (!class_exists(IsGranted::class)) {
509+
$this->markTestSkipped('The security attributes are not available.');
510+
}
511+
507512
$dehydrated = $this->dehydrateComponent($this->mountComponent('with_security'));
508513

509514
$this->browser()

src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php

+29
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
namespace Symfony\UX\LiveComponent\Tests\Functional\Test;
1313

1414
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
16+
use Symfony\Component\Security\Core\User\InMemoryUser;
17+
use Symfony\Component\Security\Http\Attribute\IsGranted;
1518
use Symfony\UX\LiveComponent\Test\InteractsWithLiveComponents;
1619
use Symfony\UX\LiveComponent\Tests\Fixtures\Component\Component2;
1720

@@ -140,4 +143,30 @@ public function testCannotUpdateComponentIfNoLongerInContext(): void
140143

141144
$testComponent->call('increase');
142145
}
146+
147+
public function testRenderingIsLazy(): void
148+
{
149+
if (!class_exists(IsGranted::class)) {
150+
$this->markTestSkipped('The security attributes are not available.');
151+
}
152+
153+
$testComponent = $this->createLiveComponent('with_security');
154+
155+
$this->expectException(AccessDeniedException::class);
156+
157+
$testComponent->render();
158+
}
159+
160+
public function testActingAs(): void
161+
{
162+
$testComponent = $this->createLiveComponent('with_security')
163+
->actingAs(new InMemoryUser('kevin', 'pass', ['ROLE_USER']))
164+
;
165+
166+
$this->assertStringNotContainsString('Username: kevin', $testComponent->render());
167+
168+
$testComponent->call('setUsername');
169+
170+
$this->assertStringContainsString('Username: kevin', $testComponent->render());
171+
}
143172
}

0 commit comments

Comments
 (0)