From 22b15922d02db57e78bd1f1b3bab02843053c3d4 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Tue, 21 Nov 2023 01:51:18 +1100 Subject: [PATCH] Allows to specify rules as closure (#71) --- functions.php | 6 +++ src/Actions/ReturnRules.php | 8 ++++ src/CompileContext.php | 2 +- ...sts_Feature_resources_view___de_php__.snap | 40 +++++++++++++++++++ tests/Feature/CompilerContext/RulesTest.php | 14 +++++++ tests/Feature/FunctionalComponentTest.php | 13 ++++++ .../component-with-rules.blade.php | 25 ++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 tests/.pest/snapshots/Feature/FunctionalComponentTest/generated_code_with_data_set__dataset__component_with_rules_blade_php______tests_Feature_resources_view___de_php__.snap create mode 100644 tests/Feature/resources/views/functional-api/component-with-rules.blade.php diff --git a/functions.php b/functions.php index 172274e..3ff9f8e 100644 --- a/functions.php +++ b/functions.php @@ -261,6 +261,12 @@ function on(Closure|array|string ...$listeners): void function rules(mixed ...$rules): RuleOptions { if (count($rules) === 1 && array_key_exists(0, $rules)) { + if ($rules[0] instanceof Closure) { + CompileContext::instance()->rules = $rules[0]; + + return new RuleOptions; + } + $rules = $rules[0]; } diff --git a/src/Actions/ReturnRules.php b/src/Actions/ReturnRules.php index 646a677..c0ec0fb 100644 --- a/src/Actions/ReturnRules.php +++ b/src/Actions/ReturnRules.php @@ -2,6 +2,8 @@ namespace Livewire\Volt\Actions; +use Closure; +use Illuminate\Container\Container; use Livewire\Volt\CompileContext; use Livewire\Volt\Component; use Livewire\Volt\Contracts\Action; @@ -13,6 +15,12 @@ class ReturnRules implements Action */ public function execute(CompileContext $context, Component $component, array $arguments): array { + if ($context->rules instanceof Closure) { + return Container::getInstance()->call( + Closure::bind($context->rules, $component, $component::class), + ); + } + return $context->rules; } } diff --git a/src/CompileContext.php b/src/CompileContext.php index f0324bf..e549860 100644 --- a/src/CompileContext.php +++ b/src/CompileContext.php @@ -23,7 +23,7 @@ public function __construct( public ?string $title, public ?Closure $listeners, public array $inlineListeners, - public array $rules, + public Closure|array $rules, public array $messages, public array $validationAttributes, public ?string $paginationView, diff --git a/tests/.pest/snapshots/Feature/FunctionalComponentTest/generated_code_with_data_set__dataset__component_with_rules_blade_php______tests_Feature_resources_view___de_php__.snap b/tests/.pest/snapshots/Feature/FunctionalComponentTest/generated_code_with_data_set__dataset__component_with_rules_blade_php______tests_Feature_resources_view___de_php__.snap new file mode 100644 index 0000000..9bca26b --- /dev/null +++ b/tests/.pest/snapshots/Feature/FunctionalComponentTest/generated_code_with_data_set__dataset__component_with_rules_blade_php______tests_Feature_resources_view___de_php__.snap @@ -0,0 +1,40 @@ +execute(static::$__context, $this, get_defined_vars()); + + (new Actions\CallHook('mount'))->execute(static::$__context, $this, get_defined_vars()); + } + + public function save() + { + $arguments = [static::$__context, $this, func_get_args()]; + + return (new Actions\CallMethod('save'))->execute(...$arguments); + } + + protected function rules() + { + return (new Actions\ReturnRules)->execute(static::$__context, $this, []); + } + + protected function messages() + { + return (new Actions\ReturnValidationMessages)->execute(static::$__context, $this, []); + } + +}; \ No newline at end of file diff --git a/tests/Feature/CompilerContext/RulesTest.php b/tests/Feature/CompilerContext/RulesTest.php index 0bf79c4..e01546d 100644 --- a/tests/Feature/CompilerContext/RulesTest.php +++ b/tests/Feature/CompilerContext/RulesTest.php @@ -32,6 +32,14 @@ ]); }); +it('may be defined using closures', function () { + $context = CompileContext::instance(); + + rules(fn () => ['name' => 'required|min:6', 'email' => 'nullable|email']); + + expect($context->rules)->resolve()->toBe(['name' => 'required|min:6', 'email' => 'nullable|email']); +}); + test('precedence', function () { $context = CompileContext::instance(); @@ -42,4 +50,10 @@ 'name' => 'second', 'email' => 'first', ]); + + rules(fn () => ['name' => 'third']); + + expect($context->rules)->resolve()->toBe([ + 'name' => 'third', + ]); }); diff --git a/tests/Feature/FunctionalComponentTest.php b/tests/Feature/FunctionalComponentTest.php index 7c640fc..45816b4 100644 --- a/tests/Feature/FunctionalComponentTest.php +++ b/tests/Feature/FunctionalComponentTest.php @@ -246,6 +246,19 @@ ]); }); +it('can have rules', function () { + $component = Livewire::test('component-with-rules'); + + $component->assertSet('saved', false) + ->call('save') + ->assertSee('The title field is missing.') + ->assertSet('saved', false) + ->updateProperty('title', 'Hello') + ->call('save') + ->assertDontSee('The title field is missing.') + ->assertSet('saved', true); +}); + it('can have reactive state', function () { $component = Livewire::test('component-with-reactive-state.todos'); diff --git a/tests/Feature/resources/views/functional-api/component-with-rules.blade.php b/tests/Feature/resources/views/functional-api/component-with-rules.blade.php new file mode 100644 index 0000000..6df7127 --- /dev/null +++ b/tests/Feature/resources/views/functional-api/component-with-rules.blade.php @@ -0,0 +1,25 @@ + '']); + +rules(fn () => ['title' => ['required', 'min:5']]) + ->messages(['title' => 'The title field is missing.']); + +$save = function () { + $this->validate(); + + $this->saved = true; +}; ?> + +
+
+ + @error('title') {{ $message }} @enderror + + +
+
+ +