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('/