Skip to content

Commit 493bc31

Browse files
committed
feature #2712 [LiveComponent] Add events assertions in InteractsWithLiveComponents (Arkalo2)
This PR was merged into the 2.x branch. Discussion ---------- [LiveComponent] Add events assertions in `InteractsWithLiveComponents` | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Docs? | yes <!-- required for new features --> | Issues | no | License | MIT This MR simplify liveComponent event testing : Actually with the [Test Helper](https://symfony.com/bundles/ux-live-component/current/index.html#test-helper), we can't simply test whether an event has been dispatched from a component. This PR add 2 asserts : ```php $this-> assertComponentEmitEvent($render, 'event-name')->withData(['eventArg1' => $eventArg1]); $this-> assertComponentEmitEvent($render, 'event-name') ->withDataSubset(['eventArg1' => $eventArg1]) ->withDataSubset(['eventArg2' => $eventArg2]) ; ``` ```php $this->assertComponentNotEmitEvent($render, 'event-name'); ``` Commits ------- edf1fc6 [LiveComponent] Add `InteractsWithLiveComponents::assertComponentEmitEvent()` to test events
2 parents 22f23b3 + edf1fc6 commit 493bc31

File tree

8 files changed

+211
-3
lines changed

8 files changed

+211
-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: 15 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,18 @@ protected function createLiveComponent(string $name, array $data = [], ?KernelBr
4445
self::getContainer()->get('router'),
4546
);
4647
}
48+
49+
protected function assertComponentEmitEvent(TestLiveComponent $testLiveComponent, string $expectedEventName): AssertEmittedEvent
50+
{
51+
$event = $testLiveComponent->getEmittedEvent($testLiveComponent->render(), $expectedEventName);
52+
53+
self::assertNotNull($event, \sprintf('The component "%s" did not emit event "%s".', $testLiveComponent->getName(), $expectedEventName));
54+
55+
return new AssertEmittedEvent($this, $event['event'], $event['data']);
56+
}
57+
58+
protected function assertComponentNotEmitEvent(TestLiveComponent $testLiveComponent, string $eventName): void
59+
{
60+
self::assertNull($testLiveComponent->getEmittedEvent($testLiveComponent->render(), $eventName), \sprintf('The component "%s" did emit event "%s".', $testLiveComponent->getName(), $eventName));
61+
}
4762
}

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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 PHPUnit\Framework\TestCase;
15+
16+
final class AssertEmittedEvent
17+
{
18+
/**
19+
* @param array<string, int|float|string|bool|null> $data
20+
*/
21+
public function __construct(
22+
private readonly TestCase $testCase,
23+
private readonly string $eventName,
24+
private readonly array $data,
25+
) {
26+
}
27+
28+
/**
29+
* @return self
30+
*/
31+
public function withDataSubset(array $expectedEventData): object
32+
{
33+
foreach ($expectedEventData as $key => $value) {
34+
$this->testCase::assertArrayHasKey($key, $this->data, \sprintf('The expected event "%s" data "%s" does not exists', $this->eventName, $key));
35+
$this->testCase::assertSame(
36+
$value,
37+
$this->data[$key],
38+
\sprintf(
39+
'The event "%s" data "%s" expect to be "%s", but "%s" given.',
40+
$this->eventName,
41+
$key,
42+
$value,
43+
$this->data[$key]
44+
)
45+
);
46+
}
47+
48+
return $this;
49+
}
50+
51+
public function withData(array $expectedEventData): void
52+
{
53+
$this->testCase::assertEquals($expectedEventData, $this->data, \sprintf('The event "%s" data is different than expected.', $this->eventName));
54+
}
55+
}

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: 73 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,76 @@ public function testSetLocaleRenderLocalizedComponent(): void
217218
$testComponent->setRouteLocale('de');
218219
$this->assertStringContainsString('Locale: de', $testComponent->render());
219220
}
221+
222+
public function testAssertComponentEmitEvent(): void
223+
{
224+
$testComponent = $this->createLiveComponent('component_with_emit');
225+
226+
$testComponent->call('actionThatEmits');
227+
228+
$this->assertComponentEmitEvent($testComponent, 'event1')
229+
->withData([
230+
'foo' => 'bar',
231+
'bar' => 'foo',
232+
]);
233+
}
234+
235+
public function testAssertComponentEmitEventFails(): void
236+
{
237+
$testComponent = $this->createLiveComponent('component_with_emit');
238+
239+
$testComponent->call('actionThatEmits');
240+
241+
$this->expectException(AssertionFailedError::class);
242+
$this->expectExceptionMessage('The event "event1" data is different than expected.');
243+
$this->assertComponentEmitEvent($testComponent, 'event1')->withData([
244+
'foo' => 'bar',
245+
]);
246+
}
247+
248+
public function testComponentEmitsExpectedPartialEventData(): void
249+
{
250+
$testComponent = $this->createLiveComponent('component_with_emit');
251+
252+
$testComponent->call('actionThatEmits');
253+
254+
$this->assertComponentEmitEvent($testComponent, 'event1')
255+
->withDataSubset(['foo' => 'bar'])
256+
->withDataSubset(['bar' => 'foo'])
257+
;
258+
}
259+
260+
public function testComponentDoesNotEmitUnexpectedEvent(): void
261+
{
262+
$testComponent = $this->createLiveComponent('component_with_emit');
263+
264+
$testComponent->call('actionThatEmits');
265+
266+
$this->assertComponentNotEmitEvent($testComponent, 'event2');
267+
}
268+
269+
public function testComponentDoesNotEmitUnexpectedEventFails(): void
270+
{
271+
$testComponent = $this->createLiveComponent('component_with_emit');
272+
273+
$testComponent->call('actionThatEmits');
274+
275+
$this->expectException(AssertionFailedError::class);
276+
$this->expectExceptionMessage('The component "component_with_emit" did emit event "event1".');
277+
$this->assertComponentNotEmitEvent($testComponent, 'event1');
278+
}
279+
280+
public function testComponentEmitsEventWithIncorrectDataFails(): void
281+
{
282+
$testComponent = $this->createLiveComponent('component_with_emit');
283+
284+
$testComponent->call('actionThatEmits');
285+
286+
$this->expectException(AssertionFailedError::class);
287+
$this->expectExceptionMessage('The event "event1" data is different than expected.');
288+
$this->assertComponentEmitEvent($testComponent, 'event1')->withData([
289+
'foo' => 'bar',
290+
'foo2' => 'bar2',
291+
]);
292+
}
220293
}

0 commit comments

Comments
 (0)