Skip to content

Commit c159411

Browse files
Arkalo2Kocal
authored andcommitted
[LiveComponent] Add InteractsWithLiveComponents::assertComponentEmitEvent() to test events
1 parent 22f23b3 commit c159411

File tree

8 files changed

+210
-3
lines changed

8 files changed

+210
-3
lines changed

src/LiveComponent/CHANGELOG.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,24 @@
11
# CHANGELOG
22

3+
## 2.27.0
4+
5+
- Add events assertions in `InteractsWithLiveComponents`:
6+
```php
7+
$testComponent = $this->createLiveComponent(name: 'MyComponent');
8+
9+
$renderedComponent = $testComponent->render();
10+
11+
// Assert that the component did emit an event named 'event'
12+
$this->assertComponentEmitEvent($render, 'event')
13+
// optionally, you can assert that the event was emitted with specific data...
14+
->withData(['arg1' => 'foo', 'arg2' => 'bar'])
15+
// ... or only with a subset of data
16+
->withDataSubset(['arg1' => 'foo']);
17+
18+
// Assert that the component did not emit an event named 'another-event'
19+
$this->assertComponentNotEmitEvent($render, 'another-event');
20+
```
21+
322
## 2.26.0
423

524
- `LiveProp`: Pass the property name as second parameter of the `modifier` callable

src/LiveComponent/doc/index.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3778,9 +3778,20 @@ uses Symfony's test client to render and make requests to your components::
37783778
// emit live events
37793779
$testComponent
37803780
->emit('increaseEvent')
3781-
->emit('increaseEvent', ['amount' => 2]) // emit a live event with arguments
3781+
->emit('increaseEvent', ['amount' => 2, 'unit' => 'kg']) // emit a live event with arguments
37823782
;
37833783

3784+
// Assert that the event was emitted
3785+
$this->assertComponentEmitEvent($testComponent->render(), 'increaseEvent')
3786+
// optionally, you can assert that the event was emitted with specific data...
3787+
->withData(['amount' => 2, 'unit' => 'kg'])
3788+
// ... or only with a subset of data
3789+
->withDataSubset(['amount' => 2])
3790+
;
3791+
3792+
// Assert that an event was not emitted
3793+
$this->assertComponentNotEmitEvent($testComponent->render(), 'decreaseEvent');
3794+
37843795
// set live props
37853796
$testComponent
37863797
->set('count', 99)

src/LiveComponent/src/Test/InteractsWithLiveComponents.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
1515
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
16+
use Symfony\UX\LiveComponent\Test\Util\AssertEmittedEvent;
1617
use Symfony\UX\TwigComponent\ComponentFactory;
1718

1819
/**
@@ -44,4 +45,21 @@ protected function createLiveComponent(string $name, array $data = [], ?KernelBr
4445
self::getContainer()->get('router'),
4546
);
4647
}
48+
49+
/**
50+
* @return object{withData: callable(array): void, withDataSubset: callable(array): object}
51+
*/
52+
protected function assertComponentEmitEvent(TestLiveComponent $testLiveComponent, string $expectedEventName): object
53+
{
54+
$event = $testLiveComponent->getEmittedEvent($testLiveComponent->render(), $expectedEventName);
55+
56+
$this->assertNotNull($event, \sprintf('The component "%s" did not emit event "%s".', $testLiveComponent->getName(), $expectedEventName));
57+
58+
return new AssertEmittedEvent($this, $event['event'], $event['data']);
59+
}
60+
61+
protected function assertComponentNotEmitEvent(TestLiveComponent $testLiveComponent, string $eventName): void
62+
{
63+
$this->assertNull($testLiveComponent->getEmittedEvent($testLiveComponent->render(), $eventName), \sprintf('The component "%s" did not emit event "%s".', $testLiveComponent->getName(), $eventName));
64+
}
4765
}

src/LiveComponent/src/Test/TestLiveComponent.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,4 +229,39 @@ private function flattenFormValues(array $values, string $prefix = ''): array
229229

230230
return $result;
231231
}
232+
233+
/**
234+
* @return ?array{data: array<string, int|float|string|bool|null>, event: non-empty-string}
235+
*/
236+
public function getEmittedEvent(RenderedComponent $render, string $eventName): ?array
237+
{
238+
$events = $this->getEmittedEvents($render);
239+
240+
foreach ($events as $event) {
241+
if ($event['event'] === $eventName) {
242+
return $event;
243+
}
244+
}
245+
246+
return null;
247+
}
248+
249+
/**
250+
* @return array<array{data: array<string, int|float|string|bool|null>, event: non-empty-string}>
251+
*/
252+
public function getEmittedEvents(RenderedComponent $render): array
253+
{
254+
$emit = $render->crawler()->filter('[data-live-name-value]')->attr('data-live-events-to-emit-value');
255+
256+
if (null === $emit) {
257+
return [];
258+
}
259+
260+
return json_decode($emit, associative: true, flags: \JSON_THROW_ON_ERROR);
261+
}
262+
263+
public function getName(): string
264+
{
265+
return $this->metadata->getName();
266+
}
232267
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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\Util;
13+
14+
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
15+
16+
final class AssertEmittedEvent
17+
{
18+
/**
19+
* @param array<string, int|float|string|bool|null> $data
20+
*/
21+
public function __construct(private KernelTestCase $parent, private readonly string $eventName, private readonly array $data)
22+
{
23+
}
24+
25+
/**
26+
* @return self
27+
*/
28+
public function withDataSubset(array $expectedEventData): object
29+
{
30+
foreach ($expectedEventData as $key => $value) {
31+
$this->parent->assertArrayHasKey($key, $this->data, \sprintf('The expected event "%s" data "%s" does not exists', $this->eventName, $key));
32+
$this->parent->assertSame(
33+
$value,
34+
$this->data[$key],
35+
\sprintf(
36+
'The expected event "%s" data "%s" expected "%s" but "%s" given',
37+
$this->eventName,
38+
$key,
39+
$value,
40+
$this->data[$key]
41+
)
42+
);
43+
}
44+
45+
return $this;
46+
}
47+
48+
public function withData(array $expectedEventData): void
49+
{
50+
$this->parent->assertEquals($expectedEventData, $this->data, \sprintf('The expected event "%s" data does not match.', $this->eventName));
51+
}
52+
}

src/LiveComponent/tests/Fixtures/Component/ComponentWithEmit.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ final class ComponentWithEmit
2929
#[LiveAction]
3030
public function actionThatEmits(): void
3131
{
32-
$this->emit('event1', ['foo' => 'bar']);
32+
$this->emit('event1', ['foo' => 'bar', 'bar' => 'foo']);
3333
$this->events = $this->liveResponder->getEventsToEmit();
3434
}
3535

src/LiveComponent/tests/Functional/LiveResponderTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public function testComponentCanEmitEvents(): void
3535
])
3636
->assertSuccessful()
3737
->assertSee('Event: event1')
38-
->assertSee('Data: {"foo":"bar"}');
38+
->assertSee('Data: {"foo":"bar","bar":"foo"}');
3939
}
4040

4141
public function testComponentCanDispatchBrowserEvents(): void

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

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

14+
use PHPUnit\Framework\AssertionFailedError;
1415
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
1516
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
1617
use Symfony\Component\Security\Core\User\InMemoryUser;
@@ -217,4 +218,75 @@ public function testSetLocaleRenderLocalizedComponent(): void
217218
$testComponent->setRouteLocale('de');
218219
$this->assertStringContainsString('Locale: de', $testComponent->render());
219220
}
221+
222+
public function testComponentEmitsExpectedEventData(): void
223+
{
224+
$testComponent = $this->createLiveComponent('component_with_emit');
225+
226+
$testComponent->call('actionThatEmits');
227+
228+
$this->assertComponentEmitEvent($testComponent, 'event1')->withData([
229+
'foo' => 'bar',
230+
'bar' => 'foo',
231+
]);
232+
}
233+
234+
public function testComponentEmitsExpectedEventDataFails(): void
235+
{
236+
$testComponent = $this->createLiveComponent('component_with_emit');
237+
238+
$testComponent->call('actionThatEmits');
239+
240+
$this->expectException(AssertionFailedError::class);
241+
$this->expectExceptionMessage('The expected event "event1" data does not match');
242+
$this->assertComponentEmitEvent($testComponent, 'event1')->withData([
243+
'foo' => 'bar',
244+
]);
245+
}
246+
247+
public function testComponentEmitsExpectedPartialEventData(): void
248+
{
249+
$testComponent = $this->createLiveComponent('component_with_emit');
250+
251+
$testComponent->call('actionThatEmits');
252+
253+
$this->assertComponentEmitEvent($testComponent, 'event1')
254+
->withDataSubset(['foo' => 'bar'])
255+
->withDataSubset(['bar' => 'foo'])
256+
;
257+
}
258+
259+
public function testComponentDoesNotEmitUnexpectedEvent(): void
260+
{
261+
$testComponent = $this->createLiveComponent('component_with_emit');
262+
263+
$testComponent->call('actionThatEmits');
264+
265+
$this->assertComponentNotEmitEvent($testComponent, 'event2');
266+
}
267+
268+
public function testComponentDoesNotEmitUnexpectedEventFails(): void
269+
{
270+
$testComponent = $this->createLiveComponent('component_with_emit');
271+
272+
$testComponent->call('actionThatEmits');
273+
274+
$this->expectException(AssertionFailedError::class);
275+
$this->expectExceptionMessage('The component "component_with_emit" did not emit event "event1".');
276+
$this->assertComponentNotEmitEvent($testComponent, 'event1');
277+
}
278+
279+
public function testComponentEmitsEventWithIncorrectDataFails(): void
280+
{
281+
$testComponent = $this->createLiveComponent('component_with_emit');
282+
283+
$testComponent->call('actionThatEmits');
284+
285+
$this->expectException(AssertionFailedError::class);
286+
$this->expectExceptionMessage('The expected event "event1" data does not match.');
287+
$this->assertComponentEmitEvent($testComponent, 'event1')->withData([
288+
'foo' => 'bar',
289+
'foo2' => 'bar2',
290+
]);
291+
}
220292
}

0 commit comments

Comments
 (0)