Files
michaelschiemer/tests/Pest.php
Michael Schiemer fc3d7e6357 feat(Production): Complete production deployment infrastructure
- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
2025-10-25 19:18:37 +02:00

270 lines
8.1 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);
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,
];
}