diff --git a/src/LiveComponent/doc/index.rst b/src/LiveComponent/doc/index.rst index 290b07bb5dc..f19ebb4ac05 100644 --- a/src/LiveComponent/doc/index.rst +++ b/src/LiveComponent/doc/index.rst @@ -3781,6 +3781,11 @@ uses Symfony's test client to render and make requests to your components:: ->emit('increaseEvent', ['amount' => 2]) // emit a live event with arguments ; + // Assert that the event was emitted + $this->componentHasEmittedEvent($render, 'increaseEvent', [ + 'amount' => 2, + ]); + // set live props $testComponent ->set('count', 99) diff --git a/src/LiveComponent/src/Test/InteractsWithLiveComponents.php b/src/LiveComponent/src/Test/InteractsWithLiveComponents.php index 377fbad1f34..31fcb7b1f38 100644 --- a/src/LiveComponent/src/Test/InteractsWithLiveComponents.php +++ b/src/LiveComponent/src/Test/InteractsWithLiveComponents.php @@ -44,4 +44,35 @@ protected function createLiveComponent(string $name, array $data = [], ?KernelBr self::getContainer()->get('router'), ); } + + protected function assertComponentEmitEvent(TestLiveComponent $testLiveComponent, string $expectedEventName, ?array $expectedEventData = null): void + { + $event = $testLiveComponent->getEmittedEvent($testLiveComponent->render(), $expectedEventName); + + $this->assertNotNull($event, \sprintf('The component "%s" did not emit event "%s".', $testLiveComponent->getName(), $expectedEventName)); + + if (null === $expectedEventData) { + return; + } + + foreach ($expectedEventData as $key => $value) { + $this->assertArrayHasKey($key, $event['data'], \sprintf('The expected event "%s" data "%s" does not exists', $event['event'], $key)); + $this->assertSame( + $value, + $event['data'][$key], + \sprintf( + 'The expected event "%s" data "%s" expected "%s" but "%s" given', + $event['event'], + $key, + $value, + $event['data'][$key] + ) + ); + } + } + + protected function assertComponentNotEmitEvent(TestLiveComponent $testLiveComponent, string $eventName): void + { + $this->assertNull($testLiveComponent->getEmittedEvent($testLiveComponent->render(), $eventName), \sprintf('The component "%s" did not emit event "%s".', $testLiveComponent->getName(), $eventName)); + } } diff --git a/src/LiveComponent/src/Test/TestLiveComponent.php b/src/LiveComponent/src/Test/TestLiveComponent.php index 0e587a38256..580c8f8415f 100644 --- a/src/LiveComponent/src/Test/TestLiveComponent.php +++ b/src/LiveComponent/src/Test/TestLiveComponent.php @@ -229,4 +229,39 @@ private function flattenFormValues(array $values, string $prefix = ''): array return $result; } + + /** + * @return ?array{data: array, event: non-empty-string} + */ + public function getEmittedEvent(RenderedComponent $render, string $eventName): ?array + { + $events = $this->getEmittedEvents($render); + + foreach ($events as $event) { + if ($event['event'] === $eventName) { + return $event; + } + } + + return null; + } + + /** + * @return array, event: non-empty-string}> + */ + public function getEmittedEvents(RenderedComponent $render): array + { + $emit = $render->crawler()->filter('[data-live-name-value]')->attr('data-live-events-to-emit-value'); + + if (null === $emit) { + return []; + } + + return json_decode($emit, associative: true, flags: \JSON_THROW_ON_ERROR); + } + + public function getName(): string + { + return $this->metadata->getName(); + } } diff --git a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php index 097e4d770f7..b671d4bffd9 100644 --- a/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php +++ b/src/LiveComponent/tests/Functional/Test/InteractsWithLiveComponentsTest.php @@ -11,6 +11,7 @@ namespace Symfony\UX\LiveComponent\Tests\Functional\Test; +use PHPUnit\Framework\AssertionFailedError; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; use Symfony\Component\Security\Core\Exception\AccessDeniedException; use Symfony\Component\Security\Core\User\InMemoryUser; @@ -217,4 +218,62 @@ public function testSetLocaleRenderLocalizedComponent(): void $testComponent->setRouteLocale('de'); $this->assertStringContainsString('Locale: de', $testComponent->render()); } + + public function testComponentEmitsExpectedEventWithExpectedEventData(): void + { + $testComponent = $this->createLiveComponent('component_with_emit'); + + $testComponent->call('actionThatEmits'); + + $this->assertComponentEmitEvent($testComponent, 'event1', [ + 'foo' => 'bar', + ]); + } + + public function testComponentDoesNotEmitUnexpectedEvent(): void + { + $testComponent = $this->createLiveComponent('component_with_emit'); + + $testComponent->call('actionThatEmits'); + + $this->assertComponentNotEmitEvent($testComponent, 'event2'); + } + + public function testComponentDoesNotEmitUnexpectedEventFails(): void + { + $testComponent = $this->createLiveComponent('component_with_emit'); + + $testComponent->call('actionThatEmits'); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('The component "component_with_emit" did not emit event "event1".'); + $this->assertComponentNotEmitEvent($testComponent, 'event1'); + } + + public function testComponentEmitsEventWithNotFoundExpectedDataFails(): void + { + $testComponent = $this->createLiveComponent('component_with_emit'); + + $testComponent->call('actionThatEmits'); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('The expected event "event1" data "foo2" does not exists'); + $this->assertComponentEmitEvent($testComponent, 'event1', [ + 'foo' => 'bar', + 'foo2' => 'bar2', + ]); + } + + public function testComponentEmitsEventWithNotValidExpectedDataFails(): void + { + $testComponent = $this->createLiveComponent('component_with_emit'); + + $testComponent->call('actionThatEmits'); + + $this->expectException(AssertionFailedError::class); + $this->expectExceptionMessage('The expected event "event1" data "foo" expected "bar2" but "bar" given'); + $this->assertComponentEmitEvent($testComponent, 'event1', [ + 'foo' => 'bar2', + ]); + } }