in('Feature'); /* |-------------------------------------------------------------------------- | Expectations |-------------------------------------------------------------------------- | | When you're writing tests, you often need to check that values meet certain conditions. The | "expect()" function gives you access to a set of "expectations" methods that you can use | to assert different things. Of course, you may extend the Expectation API at any time. | */ expect()->extend('toBeOne', function () { return $this->toBe(1); }); // LiveComponent Custom Expectations /** * Assert HTML contains text * Usage: expect($html)->toContainHtml('Count: 5') */ expect()->extend('toContainHtml', function (string $needle) { $haystack = is_string($this->value) ? $this->value : (string) $this->value; expect($haystack)->toContain($needle); }); /** * Assert HTML matches snapshot * Usage: expect($html)->toMatchSnapshot('counter-initial-state') */ expect()->extend('toMatchSnapshot', function (string $snapshotName, bool $updateSnapshot = false) { $html = is_string($this->value) ? $this->value : (string) $this->value; \Tests\Feature\Framework\LiveComponents\TestHarness\ComponentSnapshotTest::assertMatchesSnapshot( $html, $snapshotName, $updateSnapshot ); return $this; return $this; }); /** * Assert state equals expected array * Usage: expect($state)->toHaveState(['count' => 5]) */ expect()->extend('toHaveState', function (array $expected) { $actual = is_array($this->value) ? $this->value : $this->value->toArray(); expect($actual)->toBe($expected); return $this; }); /** * Assert specific state key equals value * Usage: expect($state)->toHaveStateKey('count', 5) */ expect()->extend('toHaveStateKey', function (string $key, mixed $value) { $actual = is_array($this->value) ? $this->value : $this->value->toArray(); expect($actual)->toHaveKey($key); expect($actual[$key])->toBe($value); return $this; }); /** * Assert event was dispatched * Usage: expect($events)->toHaveDispatchedEvent('counter-updated') */ expect()->extend('toHaveDispatchedEvent', function (string $eventName) { $events = $this->value; // Array von ComponentEvent VOs $found = false; foreach ($events as $event) { if ($event->name === $eventName) { $found = true; break; } } expect($found)->toBeTrue("Event '{$eventName}' was not dispatched"); return $this; }); /** * Assert event was dispatched with specific data * Usage: expect($events)->toHaveDispatchedEventWithData('user-updated', ['id' => 123]) */ expect()->extend('toHaveDispatchedEventWithData', function (string $eventName, array $expectedData) { $events = $this->value; $found = false; foreach ($events as $event) { if ($event->name === $eventName && $event->data === $expectedData) { $found = true; break; } } expect($found)->toBeTrue( "Event '{$eventName}' with data " . json_encode($expectedData) . " was not dispatched" ); return $this; }); /** * Assert HTML contains fragment with specific content * Usage: expect($html)->toHaveFragment('user-stats', 'Total: 10') */ expect()->extend('toHaveFragment', function (string $fragmentName, string $needle) { $html = is_string($this->value) ? $this->value : (string) $this->value; // Check if fragment exists $pattern = '/data-lc-fragment=["\']' . preg_quote($fragmentName, '/') . '["\']/'; expect($html)->toMatch($pattern, "Fragment '{$fragmentName}' not found in HTML"); // Check if fragment contains content expect($html)->toContain($needle); return $this; }); /* |-------------------------------------------------------------------------- | Functions |-------------------------------------------------------------------------- | | While Pest is very powerful out-of-the-box, you may have some testing code specific to your | project that you don't want to repeat in every file. Here you can also expose helpers as | global functions to help you to reduce the number of lines of code in your test files. | */ /** * Get DI Container instance */ function container(): Container { static $container = null; if ($container === null) { // Bootstrap container for tests require_once __DIR__ . '/../bootstrap/app.php'; $container = $GLOBALS['container'] ?? throw new RuntimeException('Container not bootstrapped'); } return $container; } /** * Mount a LiveComponent with initial state * * @param string $componentId Component ID (e.g., 'counter:test') * @param array $initialData Initial component data * @return array ['html' => string, 'state' => array, 'componentId' => string, 'component' => LiveComponentContract] */ function mountComponent(string $componentId, array $initialData = []): array { $registry = container()->get(ComponentRegistry::class); $renderer = container()->get(LiveComponentRenderer::class); // Resolve component instance $component = $registry->resolve(ComponentId::fromString($componentId), $initialData); // Render component $html = $renderer->render($component); // Get current state $state = $component->state->toArray(); return [ 'html' => $html, 'state' => $state, 'componentId' => $componentId, 'component' => $component, 'events' => [], // Initial mount has no events ]; } /** * Call action on a LiveComponent * * @param array $component Component array from mountComponent() * @param string $action Action name * @param array $params Action parameters * @return array ['html' => string, 'state' => array, 'events' => ComponentEvent[], 'componentId' => string] */ function callAction(array $component, string $action, array $params = []): array { $handler = container()->get(LiveComponentHandler::class); $renderer = container()->get(LiveComponentRenderer::class); // Execute action $result = $handler->handleAction( $component['component'], $action, ActionParameters::fromArray($params) ); // Render updated component $html = $renderer->render($result->component); return [ 'html' => $html, 'state' => $result->component->state->toArray(), 'events' => $result->events, 'componentId' => $component['componentId'], 'component' => $result->component, ]; } /** * Call action and expect fragments to be updated * * @param array $component Component array from mountComponent() * @param string $action Action name * @param array $params Action parameters * @param array $fragments Fragment names to extract * @return array ['fragments' => array, 'state' => array, 'events' => ComponentEvent[]] */ function callActionWithFragments(array $component, string $action, array $params = [], array $fragments = []): array { $handler = container()->get(LiveComponentHandler::class); $fragmentRenderer = container()->get(\App\Framework\LiveComponents\Rendering\FragmentRenderer::class); // Execute action $result = $handler->handleAction( $component['component'], $action, ActionParameters::fromArray($params) ); // Render fragments if requested $fragmentsHtml = []; if (!empty($fragments)) { $fragmentCollection = $fragmentRenderer->renderFragments( $result->component, $fragments ); foreach ($fragmentCollection as $fragment) { $fragmentsHtml[$fragment->name] = $fragment->content; } } return [ 'fragments' => $fragmentsHtml, 'state' => $result->component->state->toArray(), 'events' => $result->events, 'componentId' => $component['componentId'], 'component' => $result->component, ]; }