container = new DefaultContainer(); $this->validator = new ActionValidator(); } public function test_validates_valid_action(): void { $component = $this->createValidComponent(); $context = $this->createContext($component, 'validAction'); $actionAttribute = new Action(); // Should not throw $this->validator->validate($context, $actionAttribute); $this->assertTrue(true); } public function test_rejects_reserved_method(): void { $component = $this->createValidComponent(); $context = $this->createContext($component, 'onMount'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('reserved method'); $this->validator->validate($context, $actionAttribute); } public function test_rejects_non_existent_method(): void { $component = $this->createValidComponent(); $context = $this->createContext($component, 'nonExistentMethod'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('not found'); $this->validator->validate($context, $actionAttribute); } public function test_rejects_private_method(): void { $component = new PrivateActionComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); $context = $this->createContext($component, 'privateAction'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('must be public'); $this->validator->validate($context, $actionAttribute); } public function test_rejects_static_method(): void { $component = new StaticActionComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); $context = $this->createContext($component, 'staticAction'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('cannot be static'); $this->validator->validate($context, $actionAttribute); } public function test_rejects_primitive_return_type(): void { $component = new PrimitiveReturnTypeComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); $context = $this->createContext($component, 'intAction'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('must return a State object'); $this->validator->validate($context, $actionAttribute); } public function test_rejects_array_return_type(): void { $component = new ArrayReturnTypeComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); $context = $this->createContext($component, 'arrayAction'); $actionAttribute = new Action(); $this->expectException(\BadMethodCallException::class); $this->expectExceptionMessage('must return a State object'); $this->validator->validate($context, $actionAttribute); } private function createValidComponent(): LiveComponentContract { return new ValidActionComponent( ComponentId::create('test', 'demo'), CounterState::empty() ); } private function createContext(LiveComponentContract $component, string $methodName): AttributeExecutionContext { $componentClass = ClassName::create($component::class); $method = MethodName::create($methodName); $componentId = $component->id; $actionParameters = ActionParameters::fromArray([]); return LiveComponentContextHelper::createForAction( container: $this->container, componentClass: $componentClass, actionMethod: $method, componentId: $componentId, actionParameters: $actionParameters, component: $component ); } }