Skip to content

Commit 5943c89

Browse files
committed
[LiveComponent] Add dispatch browser event test
1 parent 7b19b30 commit 5943c89

File tree

9 files changed

+241
-5
lines changed

9 files changed

+241
-5
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.31
4+
5+
- Add browser events assertions in `InteractsWithLiveComponents`:
6+
```php
7+
$testComponent = $this->createLiveComponent(name: 'MyComponent');
8+
9+
$renderedComponent = $testComponent->render();
10+
11+
// Assert that the component did dispatch a browser event named 'browser:event'
12+
$this->assertComponentDispatchBrowserEvent($render, 'browser:event')
13+
// optionally, you can assert that the browser event was dispatched 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 dispatch a browser event named 'another-browser:event'
19+
$this->assertComponentNotDispatchBrowserEvent($render, 'another-browser:event');
20+
```
21+
322
## 2.30
423

524
- Ensure compatibility with PHP 8.5

src/LiveComponent/doc/index.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3851,6 +3851,23 @@ uses Symfony's test client to render and make requests to your components::
38513851
// Assert that an event was not emitted
38523852
$this->assertComponentNotEmitEvent($testComponent->render(), 'decreaseEvent');
38533853

3854+
// dispatch browser events
3855+
$testComponent
3856+
->dispatchBrowserEvent('browserEvent')
3857+
->dispatchBrowserEvent('browserEvent', ['amount' => 2, 'unit' => 'kg']) // dispatch a browser event with arguments
3858+
;
3859+
3860+
// Assert that the event was dispatched
3861+
$this->assertComponentDispatchBrowserEvent($testComponent->render(), 'browserEvent')
3862+
// optionally, you can assert that the event was dispatched with specific data...
3863+
->withPayload(['amount' => 2, 'unit' => 'kg'])
3864+
// ... or only with a subset of data
3865+
->withPayloadSubset(['amount' => 2])
3866+
;
3867+
3868+
// Assert that an event was not dispatched
3869+
$this->assertComponentNotDispatchBrowserEvent($testComponent->render(), 'otherBrowserEvent');
3870+
38543871
// set live props
38553872
$testComponent
38563873
->set('count', 99)

src/LiveComponent/src/Test/InteractsWithLiveComponents.php

Lines changed: 21 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\AssertDispatchedEvent;
1617
use Symfony\UX\LiveComponent\Test\Util\AssertEmittedEvent;
1718
use Symfony\UX\TwigComponent\ComponentFactory;
1819

@@ -59,4 +60,24 @@ protected function assertComponentNotEmitEvent(TestLiveComponent $testLiveCompon
5960
{
6061
self::assertNull($testLiveComponent->getEmittedEvent($testLiveComponent->render(), $eventName), \sprintf('The component "%s" did emit event "%s".', $testLiveComponent->getName(), $eventName));
6162
}
63+
64+
protected function assertComponentDispatchBrowserEvent(TestLiveComponent $testLiveComponent, string $expectedEventName): AssertDispatchedEvent
65+
{
66+
$event = $testLiveComponent->getDispatchedBrowserEvent($testLiveComponent->render(), $expectedEventName);
67+
68+
self::assertNotNull(
69+
$event,
70+
\sprintf('The component "%s" did no dispatch browser event "%s".', $testLiveComponent->getName(), $expectedEventName)
71+
);
72+
73+
return new AssertDispatchedEvent($this, $event['event'], $event['payload']);
74+
}
75+
76+
protected function assertComponentNotDispatchBrowserEvent(TestLiveComponent $testLiveComponent, string $eventName): void
77+
{
78+
self::assertNull(
79+
$testLiveComponent->getDispatchedBrowserEvent($testLiveComponent->render(), $eventName),
80+
\sprintf('The component "%s" did dispatch browser event "%s".', $testLiveComponent->getName(), $eventName)
81+
);
82+
}
6283
}

src/LiveComponent/src/Test/TestLiveComponent.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\UX\LiveComponent\Test;
1313

1414
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
15+
use Symfony\Component\DomCrawler\Crawler;
1516
use Symfony\Component\HttpFoundation\File\UploadedFile;
1617
use Symfony\Component\HttpFoundation\Response;
1718
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -251,7 +252,7 @@ public function getEmittedEvent(RenderedComponent $render, string $eventName): ?
251252
*/
252253
public function getEmittedEvents(RenderedComponent $render): array
253254
{
254-
$emit = $render->crawler()->filter('[data-live-name-value]')->attr('data-live-events-to-emit-value');
255+
$emit = $this->getComponentNameValue($render)->attr('data-live-events-to-emit-value');
255256

256257
if (null === $emit) {
257258
return [];
@@ -260,6 +261,41 @@ public function getEmittedEvents(RenderedComponent $render): array
260261
return json_decode($emit, associative: true, flags: \JSON_THROW_ON_ERROR);
261262
}
262263

264+
/**
265+
* @return ?array{data: array<string, int|float|string|bool|null>, event: non-empty-string}
266+
*/
267+
public function getDispatchedBrowserEvent(RenderedComponent $render, string $eventName): ?array
268+
{
269+
$events = $this->getDispatchedBrowserEvents($render);
270+
271+
foreach ($events as $event) {
272+
if ($event['event'] === $eventName) {
273+
return $event;
274+
}
275+
}
276+
277+
return null;
278+
}
279+
280+
/**
281+
* @return array<array{data: array<string, int|float|string|bool|null>, event: non-empty-string}>
282+
*/
283+
public function getDispatchedBrowserEvents(RenderedComponent $render): array
284+
{
285+
$dispatch = $this->getComponentNameValue($render)->attr('data-live-events-to-dispatch-value');
286+
287+
if (null === $dispatch) {
288+
return [];
289+
}
290+
291+
return json_decode($dispatch, associative: true, flags: \JSON_THROW_ON_ERROR);
292+
}
293+
294+
private function getComponentNameValue(RenderedComponent $render): Crawler
295+
{
296+
return $render->crawler()->filter('[data-live-name-value]');
297+
}
298+
263299
public function getName(): string
264300
{
265301
return $this->metadata->getName();
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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 AssertDispatchedEvent
17+
{
18+
/**
19+
* @param array<string, int|float|string|bool|null> $payload
20+
*/
21+
public function __construct(
22+
private readonly TestCase $testCase,
23+
private readonly string $eventName,
24+
private readonly array $payload,
25+
) {
26+
}
27+
28+
/**
29+
* @return self
30+
*/
31+
public function withPayloadSubset(array $expectedEventPayload): object
32+
{
33+
foreach ($expectedEventPayload as $key => $value) {
34+
$this->testCase::assertArrayHasKey($key, $this->payload, \sprintf('The expected event "%s" data "%s" does not exists', $this->eventName, $key));
35+
$this->testCase::assertSame(
36+
$value,
37+
$this->payload[$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->payload[$key]
44+
)
45+
);
46+
}
47+
48+
return $this;
49+
}
50+
51+
public function withPayload(array $expectedEventPayload): void
52+
{
53+
$this->testCase::assertEquals(
54+
$expectedEventPayload,
55+
$this->payload,
56+
\sprintf('The event "%s" payload is different than expected.', $this->eventName)
57+
);
58+
}
59+
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ final class ComponentWithEmit
2424
use DefaultActionTrait;
2525
use ComponentToolsTrait;
2626

27-
public $events = [];
27+
public array $events = [];
28+
public array $dispatchEvents = [];
2829

2930
#[LiveAction]
3031
public function actionThatEmits(): void
@@ -36,9 +37,13 @@ public function actionThatEmits(): void
3637
#[LiveAction]
3738
public function actionThatDispatchesABrowserEvent(): void
3839
{
39-
$this->liveResponder->dispatchBrowserEvent(
40+
$this->dispatchBrowserEvent(
4041
'browser-event',
41-
['fooKey' => 'barVal'],
42+
[
43+
'fooKey' => 'barVal',
44+
'barKey' => 'fooVal',
45+
],
4246
);
47+
$this->dispatchEvents = $this->liveResponder->getBrowserEventsToDispatch();
4348
}
4449
}

src/LiveComponent/tests/Fixtures/templates/components/component_with_emit.html.twig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@
55
Event: {{ event.event }}<br>
66
Data: {{ event.data|json_encode|raw }}<br>
77
{% endfor %}
8+
9+
{% for event in dispatchEvents %}
10+
Event: {{ event.event }}<br>
11+
Payload: {{ event.payload|json_encode|raw }}<br>
12+
{% endfor %}
813
</div>

src/LiveComponent/tests/Functional/LiveResponderTest.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ public function testComponentCanDispatchBrowserEvents()
4949
'body' => ['data' => json_encode(['props' => $dehydrated->getProps()])],
5050
])
5151
->assertSuccessful()
52+
->assertSee('Event: browser-event')
53+
->assertSee('Payload: {"fooKey":"barVal","barKey":"fooVal"}')
5254
->crawler()
5355
;
5456

@@ -57,7 +59,7 @@ public function testComponentCanDispatchBrowserEvents()
5759
$this->assertNotNull($browserDispatch);
5860
$browserDispatchData = json_decode($browserDispatch, true);
5961
$this->assertSame([
60-
['event' => 'browser-event', 'payload' => ['fooKey' => 'barVal']],
62+
['event' => 'browser-event', 'payload' => ['fooKey' => 'barVal', 'barKey' => 'fooVal']],
6163
], $browserDispatchData);
6264
}
6365
}

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

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,76 @@ public function testComponentEmitsEventWithIncorrectDataFails()
290290
'foo2' => 'bar2',
291291
]);
292292
}
293+
294+
public function testAssertComponentDispatchBrowserEvent()
295+
{
296+
$testComponent = $this->createLiveComponent('component_with_emit');
297+
298+
$testComponent->call('actionThatDispatchesABrowserEvent');
299+
300+
$this->assertComponentDispatchBrowserEvent($testComponent, 'browser-event')
301+
->withPayload([
302+
'fooKey' => 'barVal',
303+
'barKey' => 'fooVal',
304+
]);
305+
}
306+
307+
public function testAssertComponentDispatchBrowserEventFails()
308+
{
309+
$testComponent = $this->createLiveComponent('component_with_emit');
310+
311+
$testComponent->call('actionThatDispatchesABrowserEvent');
312+
313+
$this->expectException(AssertionFailedError::class);
314+
$this->expectExceptionMessage('The event "browser-event" payload is different than expected.');
315+
$this->assertComponentDispatchBrowserEvent($testComponent, 'browser-event')->withPayload([
316+
'fooKey' => 'barVal',
317+
]);
318+
}
319+
320+
public function testComponentDispatchesExpectedPartialBrowserEventData()
321+
{
322+
$testComponent = $this->createLiveComponent('component_with_emit');
323+
324+
$testComponent->call('actionThatDispatchesABrowserEvent');
325+
326+
$this->assertComponentDispatchBrowserEvent($testComponent, 'browser-event')
327+
->withPayloadSubset(['fooKey' => 'barVal'])
328+
->withPayloadSubset(['barKey' => 'fooVal'])
329+
;
330+
}
331+
332+
public function testComponentDoesNotDispatchUnexpectedBrowserEvent()
333+
{
334+
$testComponent = $this->createLiveComponent('component_with_emit');
335+
336+
$testComponent->call('actionThatDispatchesABrowserEvent');
337+
338+
$this->assertComponentNotDispatchBrowserEvent($testComponent, 'browser-event2');
339+
}
340+
341+
public function testComponentDoesNotDispatchUnexpectedBrowserEventFails()
342+
{
343+
$testComponent = $this->createLiveComponent('component_with_emit');
344+
345+
$testComponent->call('actionThatDispatchesABrowserEvent');
346+
347+
$this->expectException(AssertionFailedError::class);
348+
$this->expectExceptionMessage('The component "component_with_emit" did dispatch browser event "browser-event".');
349+
$this->assertComponentNotDispatchBrowserEvent($testComponent, 'browser-event');
350+
}
351+
352+
public function testComponentDispatchesBrowserEventWithIncorrectDataFails()
353+
{
354+
$testComponent = $this->createLiveComponent('component_with_emit');
355+
356+
$testComponent->call('actionThatDispatchesABrowserEvent');
357+
358+
$this->expectException(AssertionFailedError::class);
359+
$this->expectExceptionMessage('The event "browser-event" payload is different than expected.');
360+
$this->assertComponentDispatchBrowserEvent($testComponent, 'browser-event')->withPayload([
361+
'fooKey' => 'barVal',
362+
'fooKey2' => 'barVal2',
363+
]);
364+
}
293365
}

0 commit comments

Comments
 (0)