Some checks failed
Deploy Application / deploy (push) Has been cancelled
286 lines
8.6 KiB
PHP
286 lines
8.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\DI\Container;
|
|
use App\Framework\LiveComponents\ComponentRegistry;
|
|
use App\Framework\LiveComponents\LiveComponentHandler;
|
|
use App\Framework\LiveComponents\ValueObjects\ActionParameters;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentUpdate;
|
|
use App\Framework\View\LiveComponentRenderer;
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Test Case
|
|
|--------------------------------------------------------------------------
|
|
|
|
|
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
|
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
|
| need to change it using the "uses()" function to bind a different classes or traits.
|
|
|
|
|
*/
|
|
|
|
// uses(Tests\TestCase::class)->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,
|
|
];
|
|
}
|