- 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.
202 lines
6.8 KiB
PHP
202 lines
6.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Framework\Http\Status;
|
|
|
|
describe('Fragment Update Integration', function () {
|
|
it('updates single fragment via action', function () {
|
|
// Create counter component
|
|
$createResponse = $this->post('/live-component/counter:demo/create', [
|
|
'initial_count' => 0,
|
|
]);
|
|
|
|
expect($createResponse->status)->toBe(Status::OK);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
// Request fragment update
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'increment',
|
|
'params' => ['amount' => 5],
|
|
'state' => ['count' => 0],
|
|
'fragments' => ['counter-display'],
|
|
]);
|
|
|
|
expect($updateResponse->status)->toBe(Status::OK);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
|
|
// Should return fragments instead of full HTML
|
|
expect($data)->toHaveKey('fragments');
|
|
expect($data)->not->toHaveKey('html');
|
|
|
|
expect($data['fragments'])->toHaveKey('counter-display');
|
|
expect($data['fragments']['counter-display'])->toContain('5');
|
|
|
|
// Should still return updated state
|
|
expect($data['state']['count'])->toBe(5);
|
|
});
|
|
|
|
it('updates multiple fragments simultaneously', function () {
|
|
$createResponse = $this->post('/live-component/form:demo/create', [
|
|
'email' => '',
|
|
'errors' => [],
|
|
]);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
// Request multiple fragment updates
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'validate',
|
|
'params' => ['email' => 'invalid-email'],
|
|
'state' => ['email' => '', 'errors' => []],
|
|
'fragments' => ['form-input', 'form-errors'],
|
|
]);
|
|
|
|
expect($updateResponse->status)->toBe(Status::OK);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
$fragments = $data['fragments'];
|
|
|
|
expect($fragments)->toHaveKey('form-input');
|
|
expect($fragments)->toHaveKey('form-errors');
|
|
|
|
// Input fragment should reflect new value
|
|
expect($fragments['form-input'])->toContain('invalid-email');
|
|
|
|
// Errors fragment should show validation error
|
|
expect($fragments['form-errors'])->toContain('email');
|
|
});
|
|
|
|
it('falls back to full render when fragments not found', function () {
|
|
$createResponse = $this->post('/live-component/counter:demo/create', [
|
|
'initial_count' => 0,
|
|
]);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
// Request non-existent fragment
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'increment',
|
|
'params' => ['amount' => 1],
|
|
'state' => ['count' => 0],
|
|
'fragments' => ['non-existent-fragment'],
|
|
]);
|
|
|
|
expect($updateResponse->status)->toBe(Status::OK);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
|
|
// Should fall back to full HTML
|
|
expect($data)->toHaveKey('html');
|
|
expect($data)->not->toHaveKey('fragments');
|
|
});
|
|
|
|
it('preserves events with fragment updates', function () {
|
|
$createResponse = $this->post('/live-component/counter:demo/create', [
|
|
'initial_count' => 0,
|
|
]);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'increment',
|
|
'params' => ['amount' => 10],
|
|
'state' => ['count' => 0],
|
|
'fragments' => ['counter-display'],
|
|
]);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
|
|
// Should include events even with fragment update
|
|
expect($data)->toHaveKey('events');
|
|
expect($data['events'])->toBeArray();
|
|
|
|
// If counter dispatches increment event
|
|
if (count($data['events']) > 0) {
|
|
expect($data['events'][0])->toHaveKey('type');
|
|
}
|
|
});
|
|
|
|
it('handles empty fragments array as full render', function () {
|
|
$createResponse = $this->post('/live-component/counter:demo/create', [
|
|
'initial_count' => 0,
|
|
]);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
// Empty fragments array should trigger full render
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'increment',
|
|
'params' => ['amount' => 1],
|
|
'state' => ['count' => 0],
|
|
'fragments' => [],
|
|
]);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
|
|
// Should return full HTML, not fragments
|
|
expect($data)->toHaveKey('html');
|
|
expect($data)->not->toHaveKey('fragments');
|
|
});
|
|
|
|
it('updates fragments with complex nested HTML', function () {
|
|
$createResponse = $this->post('/live-component/card:demo/create', [
|
|
'title' => 'Original Title',
|
|
'content' => 'Original Content',
|
|
]);
|
|
|
|
$componentId = $createResponse->jsonData['id'];
|
|
|
|
$updateResponse = $this->post("/live-component/{$componentId}", [
|
|
'method' => 'updateTitle',
|
|
'params' => ['title' => 'New Title'],
|
|
'state' => ['title' => 'Original Title', 'content' => 'Original Content'],
|
|
'fragments' => ['card-header'],
|
|
]);
|
|
|
|
$data = $updateResponse->jsonData;
|
|
|
|
if (isset($data['fragments']['card-header'])) {
|
|
$header = $data['fragments']['card-header'];
|
|
|
|
// Should contain new title
|
|
expect($header)->toContain('New Title');
|
|
|
|
// Should preserve HTML structure
|
|
expect($header)->toMatch('/<div[^>]*data-fragment="card-header"[^>]*>/');
|
|
}
|
|
});
|
|
|
|
it('handles concurrent fragment updates independently', function () {
|
|
$create1 = $this->post('/live-component/counter:demo1/create', ['initial_count' => 0]);
|
|
$create2 = $this->post('/live-component/counter:demo2/create', ['initial_count' => 10]);
|
|
|
|
$id1 = $create1->jsonData['id'];
|
|
$id2 = $create2->jsonData['id'];
|
|
|
|
// Update both concurrently with fragments
|
|
$update1 = $this->post("/live-component/{$id1}", [
|
|
'method' => 'increment',
|
|
'params' => ['amount' => 5],
|
|
'state' => ['count' => 0],
|
|
'fragments' => ['counter-display'],
|
|
]);
|
|
|
|
$update2 = $this->post("/live-component/{$id2}", [
|
|
'method' => 'decrement',
|
|
'params' => ['amount' => 3],
|
|
'state' => ['count' => 10],
|
|
'fragments' => ['counter-display'],
|
|
]);
|
|
|
|
// Both should succeed independently
|
|
expect($update1->status)->toBe(Status::OK);
|
|
expect($update2->status)->toBe(Status::OK);
|
|
|
|
expect($update1->jsonData['state']['count'])->toBe(5);
|
|
expect($update2->jsonData['state']['count'])->toBe(7);
|
|
});
|
|
});
|