slotManager = new SlotManager(); }); describe('CardComponent', function () { it('renders with custom header, body and footer slots', function () { $component = new CardComponent( id: ComponentId::generate(), state: ComponentState::fromArray([]) ); $providedSlots = [ SlotContent::named('header', '
User details...
'), SlotContent::named('footer', ''), ]; // Validate slots $errors = $this->slotManager->validateSlots($component, $providedSlots); expect($errors)->toBeEmpty(); // Resolve each slot $definitions = $component->getSlotDefinitions(); $headerContent = $this->slotManager->resolveSlotContent( $component, $definitions[0], // header $providedSlots ); $bodyContent = $this->slotManager->resolveSlotContent( $component, $definitions[1], // body $providedSlots ); $footerContent = $this->slotManager->resolveSlotContent( $component, $definitions[2], // footer $providedSlots ); expect($headerContent)->toContain('User Profile'); expect($bodyContent)->toContain('User details'); expect($footerContent)->toContain('Edit'); }); it('validates that body slot is required', function () { $component = new CardComponent( id: ComponentId::generate(), state: ComponentState::fromArray([]) ); // Only provide header, skip required body $providedSlots = [ SlotContent::named('header', 'Body content
'), ]; $definitions = $component->getSlotDefinitions(); $headerContent = $this->slotManager->resolveSlotContent( $component, $definitions[0], // header $providedSlots ); expect($headerContent)->toContain('card-header-default'); }); }); describe('ModalComponent', function () { it('renders with scoped context in content and actions slots', function () { $modalId = ComponentId::generate(); $component = new ModalComponent( id: $modalId, state: ComponentState::fromArray(['isOpen' => true]) ); $providedSlots = [ SlotContent::named('title', 'Modal ID: {context.modalId}
'), SlotContent::named('actions', ''), ]; $definitions = $component->getSlotDefinitions(); $contentSlot = $this->slotManager->resolveSlotContent( $component, $definitions[1], // content (scoped) $providedSlots ); $actionsSlot = $this->slotManager->resolveSlotContent( $component, $definitions[2], // actions (scoped) $providedSlots ); // Check that context was injected expect($contentSlot)->toContain($modalId->toString()); expect($actionsSlot)->toContain("closeModal('{$modalId->toString()}')"); }); it('validates that content slot is required', function () { $component = new ModalComponent( id: ComponentId::generate(), state: ComponentState::fromArray([]) ); // Only provide title, skip required content $providedSlots = [ SlotContent::named('title', 'Content
'), SlotContent::named('title', 'Body
'), ]; $definitions = $component->getSlotDefinitions(); // Header gets wrapped in div.card-header $headerContent = $this->slotManager->resolveSlotContent( $component, $definitions[0], $providedSlots ); expect($headerContent)->toContain('{context.modalId}
'), ]; $definitions = $component->getSlotDefinitions(); $content = $this->slotManager->resolveSlotContent( $component, $definitions[1], // content (scoped) $providedSlots ); // modalId is a ComponentId, which gets htmlspecialchars treatment // Check that HTML entities are properly escaped expect($content)->not->toContain('