Find this useful? Enter your email to receive occasional updates for securing PHP code.
Signing you up...
Thank you for signing up!
PHP Decode
<?php namespace Livewire\Features\SupportValidation; use Tests\TestComponent; use Livewi..
Decoded Output download
<?php
namespace Livewire\Features\SupportValidation;
use Tests\TestComponent;
use Livewire\Wireable;
use Livewire\Livewire;
use Livewire\Exceptions\MissingRulesException;
use Livewire\Component;
use Livewire\Attributes\Validate;
use Livewire\Attributes\Rule;
use Illuminate\Support\ViewErrorBag;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Collection;
class UnitTest extends \Tests\TestCase
{
public function test_update_triggers_rule_attribute()
{
Livewire::test(new class extends TestComponent {
#[Validate('required')]
public $foo = '';
#[Validate('required')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
]);
}
public function test_deprecated_rule_alias_can_be_used()
{
Livewire::test(new class extends TestComponent {
#[Rule('required')]
public $foo = '';
#[Rule('required')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
]);
}
public function test_validate_can_be_used_without_a_rule()
{
Livewire::test(new class extends TestComponent {
#[Validate]
public $foo = '';
public $bar = '';
public function rules()
{
return [
'foo' => 'required',
'bar' => 'required',
];
}
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required']);
}
public function test_realtime_validation_can_be_opted_out_of()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', onUpdate: false)]
public $foo = '';
#[Validate('required|min:3')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->assertHasNoErrors()
->set('bar', 'te')
->assertHasErrors()
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', 'te')
->assertHasNoErrors()
->call('save')
->assertHasErrors();
}
public function test_rule_attribute_supports_custom_attribute()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', attribute: 'The Foo')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The The Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_attribute_as_alias()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'The Foo')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The The Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_is_translatable()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'translatable.foo')]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The Translated Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_is_translatable_with_array()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: ['foo' => 'translatable.foo'])]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The Translated Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_translation_can_be_opted_out()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'translatable.foo', translate: false)]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The translatable.foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_messages()
{
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: 'Your foo is too short.')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_messages_when_using_repeated_attributes()
{
Livewire::test(new class extends TestComponent {
#[Validate('required', message: 'Please provide a post title')]
#[Validate('min:3', message: 'This title is too short')]
public $title = '';
})
->set('title', '')
->assertHasErrors(['title' => 'required'])
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Please provide a post title', $messages['title'][0]);
})
;
}
public function test_rule_attribute_message_is_translatable()
{
Lang::addLines(['translatable.foo' => 'Your foo is too short.'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: 'translatable.foo')]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_message_is_translatable_with_array()
{
Lang::addLines(['translatable.foo' => 'Your foo is too short.'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: ['min' => 'translatable.foo'])]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attributes_can_contain_rules_for_multiple_properties()
{
Livewire::test(new class extends TestComponent {
#[Validate(['foo' => 'required', 'bar' => 'required'])]
public $foo = '';
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', '')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
'bar' => 'required',
]);
}
public function test_rule_attributes_can_contain_multiple_rules()
{
Livewire::test(new class extends TestComponent {
#[Validate(['required', 'min:2', 'max:3'])]
public $foo = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
;
}
public function test_rule_attributes_can_contain_multiple_rules_userland(): void
{
Livewire::test(new class extends TestComponent {
#[\Livewire\Attributes\Validate('required')]
#[\Livewire\Attributes\Validate('min:2')]
#[\Livewire\Attributes\Validate('max:3')]
public $foo = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
;
}
public function test_rule_attributes_can_be_repeated()
{
Livewire::test(new class extends TestComponent {
#[Validate('required')]
#[Validate('min:2')]
#[Validate('max:3')]
public $foo = '';
#[
Validate('sometimes'),
Validate('max:1')
]
public $bar = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
->set('bar', '12')
->assertHasErrors(['bar' => 'max'])
->set('bar', '1')
->assertHasNoErrors()
;
}
public function test_validate_with_rules_property()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('foo', '')
->call('save')
->assertHasErrors(['foo' => 'required']);
}
public function test_validate_only_with_rules_property()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('bar', '')
->assertHasErrors(['bar' => 'required']);
}
public function test_validate_without_rules_property_and_no_args_throws_exception()
{
$this->expectException(MissingRulesException::class);
Livewire::test(ComponentWithoutRulesProperty::class)->call('save');
}
public function test_can_validate_collection_properties()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('foo', 'filled')
->call('save')
->assertHasErrors('baz.*.foo')
->set('baz.0.foo', 123)
->set('baz.1.foo', 456)
->call('save')
->assertHasNoErrors('baz.*.foo');
}
public function test_validate_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidation');
$this->assertStringNotContainsString('The foo field is required', $component->html());
$this->assertStringContainsString('The bar field is required', $component->html());
}
public function test_validate_component_properties_with_custom_message()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomMessage');
$this->assertStringContainsString('Custom Message', $component->html());
}
public function test_validate_component_properties_with_custom_message_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithMessageProperty');
$this->assertStringContainsString('Custom Message', $component->html());
}
public function test_validate_component_properties_with_custom_attribute_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithAttributesProperty');
$this->assertStringContainsString('The foobar field is required.', $component->html());
$this->assertStringContainsString('The Items Baz field is required.', $component->html());
}
public function test_validate_component_properties_with_custom_attribute()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomAttribute');
$this->assertStringContainsString('The foobar field is required.', $component->html());
}
public function test_validate_component_properties_with_custom_value_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomValuesProperty');
$this->assertStringContainsString('The bar field is required when foo is my custom value.', $component->html());
}
public function test_validate_nested_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runNestedValidation');
$this->assertStringContainsString('The emails.1 field must be a valid email address.', $component->effects['html']);
}
public function test_validate_deeply_nested_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runDeeplyNestedValidation');
$this->assertStringContainsString('items.1.baz field is required', $component->html());
$this->assertStringNotContainsString('items.0.baz field is required', $component->html());
}
public function test_validation_errors_persist_across_requests()
{
$component = Livewire::test(ForValidation::class);
$component->call('runValidation')
->assertSee('The bar field is required')
->set('foo', 'bar')
->assertSee('The bar field is required');
}
public function test_old_validation_errors_are_overwritten_if_new_request_has_errors()
{
$component = Livewire::test(ForValidation::class);
$component->call('runValidation')
->set('foo', '')
->call('runValidation')
->call('$refresh')
->assertSee('The foo field is required');
}
public function test_old_validation_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', '')
->set('bar', '')
->call('runValidation')
->assertSee('The foo field is required')
->assertSee('The bar field is required')
->set('foo', 'foo')
->set('bar', 'bar')
->call('runValidation')
->assertDontSee('The foo field is required')
->assertDontSee('The bar field is required');
}
public function test_can_validate_only_a_specific_field_and_preserve_other_validation_messages()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', '')
->call('runValidation')
->assertDontSee('The foo field is required')
->assertSee('The bar field is required')
->set('foo', '')
->call('runValidationOnly', 'foo')
->assertSee('The foo field is required')
->assertSee('The bar field is required');
}
public function test_validating_only_a_specific_field_wont_throw_an_error_if_the_field_doesnt_exist()
{
$component = Livewire::test(ForValidation::class);
$component
->set('bar', '')
->call('runValidationOnlyWithFooRules', 'bar')
->assertDontSee('The foo field is required')
->assertDontSee('The bar field is required');
}
public function test_validating_only_a_specific_field_wont_throw_an_error_if_the_array_key_doesnt_exist()
{
$component = Livewire::test(ForValidation::class);
$component
->set('items', [])
->call('runDeeplyNestedValidationOnly', 'items.0.baz')
->assertSee('items.0.baz field is required');
}
public function test_can_validate_only_a_specific_field_with_custom_message_property()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', '')
->call('runValidationOnlyWithMessageProperty', 'foo')
->assertDontSee('Foo Message') // Foo is set, no error
->assertDontSee('Bar Message') // Bar is not being validated, don't show
->set('foo', '')
->call('runValidationOnlyWithMessageProperty', 'bar')
->assertDontSee('Foo Message') // Foo is not being validated, don't show
->assertSee('Bar Message'); // Bar is not set, show message
}
public function test_can_validate_only_a_specific_field_with_custom_attributes_property()
{
$component = Livewire::test(ForValidation::class);
$component
->call('runValidationOnlyWithAttributesProperty', 'bar')
->assertSee('The foobar field is required.')
->call('runValidationOnlyWithAttributesProperty', 'items.*.baz') // Test wildcard works
->assertSee('The Items Baz field is required.')
->call('runValidationOnlyWithAttributesProperty', 'items.1.baz') // Test specific instance works
->assertSee('The Items Baz field is required.')
;
}
public function test_can_validate_only_a_specific_field_with_deeply_nested_array()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.0.baz')
->assertDontSee('items.0.baz field is required')
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required');
}
public function test_old_deeply_nested_wildcard_validation_only_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required');
}
public function test_old_deeply_nested_wildcard_validation_only_is_cleared_if_new_validation_fails()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->set('items.0.baz', '')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required')
->assertSee('items.0.baz field is required');
}
public function test_old_deeply_nested_specific_validation_only_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertDontSee('items.1.baz field is required');
}
public function test_old_deeply_nested_specific_validation_only_is_cleared_if_new_validation_fails()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->set('items.0.baz', '')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required')
->assertSee('items.0.baz field is required');
}
public function test_validation_errors_are_shared_for_all_views()
{
$component = Livewire::test(ForValidation::class);
app('view')->share('errors', $errors = new ViewErrorBag);
$component
->set('bar', '')
->call('runValidation')
->assertSee('sharedError:The bar field is required');
$this->assertTrue(app('view')->shared('errors') === $errors);
}
public function test_validation_errors_are_shared_when_redirecting_back_to_full_page_component()
{
// We apply the web middleware here so that the errors will get shared
// from the session to the view via ShareErrorsFromSession middleware
Route::get('/full-page-component', ForValidation::class)->middleware('web');
Route::post('/non-livewire-form', function () {
request()->validate(['bar' => 'required']);
});
$post = $this
->from('/full-page-component')
->followingRedirects()
->post(url('/non-livewire-form'))
->assertSee('sharedError:The bar field is required');
}
public function test_multi_word_validation_rules_failing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'bar123&*(O)')
->call('runValidationWithMultiWordRule')
->assertHasErrors(['foo' => 'alpha_dash']);
}
public function test_class_based_validation_rules_failing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'barbaz')
->call('runValidationWithClassBasedRule')
->assertHasErrors(['foo' => ValueEqualsFoobar::class]);
}
public function test_can_assert_has_no_errors_when_no_validation_has_failed_and_specific_keys_are_supplied()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', 'bar')
->call('runValidation')
->assertHasNoErrors(['foo' => 'required']);
}
public function test_multi_word_validation_rules_passing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo-bar-baz')
->call('runValidationWithMultiWordRule')
->assertHasNoErrors(['foo' => 'alpha_dash']);
}
public function test_class_based_validation_rules_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foobar')
->call('runValidationWithClassBasedRule')
->assertHasNoErrors(['foo' => ValueEqualsFoobar::class]);
}
public function test_custom_validation_messages_are_cleared_between_validate_only_validations()
{
$component = Livewire::test(ForValidation::class);
// cleared when custom validation passes
$component
->set('foo', 'foo')
->set('bar', 'b')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertSee('Lengths must be the same')
->set('bar', 'baz')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertDontSee('Lengths must be the same');
// cleared when custom validation isn't run
$component
->set('foo', 'foo')
->set('bar', 'b')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertSee('Lengths must be the same')
->set('bar', '')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertSee('The bar field is required')
->assertDontSee('Lengths must be the same');
}
public function test_validation_fails_when_same_rule_is_used_without_matching()
{
Livewire::test(ForValidation::class)
->set('password', 'supersecret')
->call('runSameValidation')
->assertSee('The password field must match password confirmation.');
}
public function test_validation_passes_when_same_rule_is_used_and_matches()
{
Livewire::test(ForValidation::class)
->set('password', 'supersecret')
->set('passwordConfirmation', 'supersecret')
->call('runSameValidation')
->assertDontSee('The password field must match password confirmation.');
}
public function test_only_data_in_validation_rules_is_returned()
{
$component = new ForValidation();
$component->bar = 'is required';
$validatedData = $component->runValidationWithoutAllPublicPropertiesAndReturnValidatedData();
$this->assertSame([
'bar' => $component->bar,
], $validatedData);
}
public function test_can_assert_validation_errors_on_errors_thrown_from_custom_validator()
{
$component = Livewire::test(ForValidation::class);
$component->call('failFooOnCustomValidator')->assertHasErrors('plop');
}
public function test_can_use_withvalidator_method()
{
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidationWithClosure')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidationWithThisMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidateOnlyWithClosure')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidateOnlyWithThisMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('clearWithValidatorAfterRunningValidateMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('clearWithValidatorAfterRunningValidateOnlyMethod')->assertSetStrict('count', 1);
}
public function test_a_set_of_items_will_validate_individually()
{
Livewire::test(ValidatesOnlyTestComponent::class, ['image' => 'image', 'imageAlt' => 'This is an image'])
->call('runValidateOnly', 'image_alt')
->assertHasNoErrors(['image_alt', 'image_url', 'image'])
->call('runValidateOnly', 'image_url')
->assertHasNoErrors(['image', 'image_url', 'image_alt'])
->call('runValidateOnly', 'image')
->assertHasNoErrors(['image', 'image_url', 'image_alt']);
}
public function test_a_computed_property_is_able_to_validate()
{
Livewire::test(ValidatesComputedProperty::class, ['helper' => 10])
->call('runValidation')
->assertHasNoErrors(['computed'])
->set('helper', -1)
->call('runValidation')
->assertHasErrors(['computed']);
$this->expectExceptionMessage('No property found for validation: [missing]');
Livewire::test(ForValidation::class)
->call('runValidationOnlyWithMissingProperty', 'missing');
$this->expectExceptionMessage('No property found for validation: [fail]');
Livewire::test(ValidatesComputedProperty::class)
->call('runValidationRuleWithoutProperty');
}
public function test_when_unwrapping_data_for_validation_an_object_is_checked_if_it_is_wireable_first()
{
$this->markTestSkipped('Not sure we support setting data on a wireable without requiring a ->set method on the wireable...');
Livewire::test(ValidatesWireableProperty::class)
->call('runValidation')
->assertHasErrors('customCollection.0.amount')
->set('customCollection.0.amount', 150)
->call('runValidation')
->assertHasNoErrors('customCollection.0.amount')
;
}
public function test_adding_validation_error_inside_mount_method()
{
Livewire::test(AddErrorInMount::class)
->call('addErrors')
->assertSee('first error')
->assertSee('second error')
->assertSee('third error')
->assertHasErrors(['first', 'second', 'third'])
->call('addFilterErrors')
->assertSee('first error')
->assertSee('second error')
->assertHasErrors(['first', 'second']);
Livewire::test(AddErrorInMount::class)
->assertSee('first error')
->assertSee('second error')
->assertSee('third error')
->assertHasErrors(['first', 'second', 'third'])
->call('addFilterErrors')
->assertSee('first error')
->assertSee('second error')
->assertHasErrors(['first', 'second']);
}
}
class ComponentWithRulesProperty extends TestComponent
{
public $foo;
public $bar = 'baz';
public $baz;
protected $rules = [
'foo' => 'required',
'bar' => 'required',
'baz.*.foo' => 'numeric',
];
public function mount()
{
$this->baz = collect([
['foo' => 'a'],
['foo' => 'b'],
]);
}
public function updatedBar()
{
$this->validateOnly('bar');
}
public function save()
{
$this->validate();
}
}
class ComponentWithoutRulesProperty extends TestComponent
{
public $foo;
public function save()
{
$this->validate();
}
}
class ForValidation extends Component
{
public $foo = 'foo';
public $bar = '';
public $emails = ['[email protected]', 'invalid-email'];
public $items = [
['foo' => 'bar', 'baz' => 'blab'],
['foo' => 'bar', 'baz' => ''],
];
public $password = '';
public $passwordConfirmation = '';
public $messages = [];
public $validationAttributes = [];
public $validationCustomValues = [];
public function runValidation()
{
$this->validate([
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationWithMultiWordRule()
{
$this->validate([
'foo' => 'alpha_dash',
]);
}
public function runValidationWithClassBasedRule()
{
$this->validate([
'foo' => [new ValueEqualsFoobar],
]);
}
public function runValidationOnly($field)
{
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationOnlyWithMissingProperty($field)
{
$this->validateOnly($field, [
'missing' => 'required',
]);
}
public function runValidationOnlyWithFooRules($field)
{
$this->validateOnly($field, [
'foo' => 'required',
]);
}
public function runValidationOnlyWithCustomValidation($field)
{
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
Validator::make(
[
'foo_length' => strlen($this->foo),
'bar_length' => strlen($this->bar),
],
[ 'foo_length' => 'same:bar_length' ],
[ 'same' => 'Lengths must be the same' ]
)->validate();
}
public function runValidationOnlyWithMessageProperty($field)
{
$this->messages = [
'foo.required' => 'Foo Message',
'bar.required' => 'Bar Message',
];
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationOnlyWithAttributesProperty($field)
{
$this->validationAttributes = [
'bar' => 'foobar',
'items.*.baz' => 'Items Baz',
];
$this->validateOnly($field, [
'bar' => 'required',
'items.*.baz' => 'required',
]);
}
public function runDeeplyNestedValidationOnly($field)
{
$this->validateOnly($field, [
'items' => ['required', 'array'],
'items.*' => 'array',
'items.*.foo' => ['required', 'string'],
'items.*.baz' => ['required', 'string'],
]);
}
public function runValidationWithCustomMessage()
{
$this->validate([
'bar' => 'required',
], ['required' => 'Custom Message']);
}
public function runValidationWithMessageProperty()
{
$this->messages = [
'required' => 'Custom Message'
];
$this->validate([
'bar' => 'required'
]);
}
public function runValidationWithAttributesProperty()
{
$this->validationAttributes = [
'bar' => 'foobar',
'items.*.baz' => 'Items Baz'
];
$this->validate([
'bar' => 'required',
'items.*.baz' => 'required',
]);
}
public function runValidationWithCustomAttribute()
{
$this->validate([
'bar' => 'required',
], [], ['bar' => 'foobar']);
}
public function runValidationWithCustomValuesProperty()
{
$this->foo = 'my';
$this->validationCustomValues = [
'foo' => [
'my' => 'my custom value',
],
];
$this->validate([
'bar' => 'required_if:foo,my',
]);
}
public function runNestedValidation()
{
$this->validate([
'emails.*' => 'email',
]);
}
public function runDeeplyNestedValidation()
{
$this->validate([
'items' => ['required', 'array'],
'items.*' => 'array',
'items.*.foo' => ['required', 'string'],
'items.*.baz' => ['required', 'string'],
]);
}
public function runSameValidation()
{
$this->validate([
'password' => 'same:passwordConfirmation',
]);
}
public function runValidationWithoutAllPublicPropertiesAndReturnValidatedData()
{
return $this->validate(['bar' => 'required']);
}
public function failFooOnCustomValidator()
{
Validator::make([], ['plop' => 'required'])->validate();
}
public function render()
{
return app('view')->make('dump-errors');
}
}
class ValueEqualsFoobar implements \Illuminate\Contracts\Validation\Rule
{
public function passes($attribute, $value)
{
return $value === 'foobar';
}
public function message()
{
return '';
}
}
class WithValidationMethod extends Component
{
public $foo = 'bar';
public $count = 0;
public function runValidationWithClosure()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validate([
'foo' => 'required',
]);
}
public function runValidateOnlyWithClosure()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validateOnly('foo', [
'foo' => 'required',
]);
}
public function runValidationWithThisMethod()
{
$this->withValidator([$this, 'doSomethingWithValidator'])->validate([
'foo' => 'required',
]);
}
public function runValidateOnlyWithThisMethod()
{
$this->withValidator([$this, 'doSomethingWithValidator'])->validateOnly('foo', [
'foo' => 'required',
]);
}
public function clearWithValidatorAfterRunningValidateMethod()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validate([
'foo' => 'required',
]);
$this->validate(['foo' => 'required']);
}
public function clearWithValidatorAfterRunningValidateOnlyMethod()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validateOnly('foo', [
'foo' => 'required',
]);
$this->validateOnly('foo', ['foo' => 'required']);
}
protected function doSomethingWithValidator($validator)
{
$validator->after(function ($validator) {
$this->count++;
});
}
public function render()
{
return app('view')->make('dump-errors');
}
}
class ValidatesOnlyTestComponent extends TestComponent
{
public $image = '';
public $image_alt = '';
public $image_url = '';
public $rules = [
'image' => 'required_without:image_url|string',
'image_alt' => 'required|string',
'image_url' => 'required_without:image|string'
];
public function mount($image, $imageAlt, $imageUrl = '')
{
$this->image = $image;
$this->image_alt = $imageAlt;
$this->image_url = $imageUrl;
}
public function runValidation()
{
$this->validate();
}
public function runValidateOnly($propertyName)
{
$this->validateOnly($propertyName);
}
public function runResetValidation()
{
$this->resetValidation();
}
}
class ValidatesComputedProperty extends TestComponent
{
public $helper;
public $rules = [
'computed' => 'required|gte:0'
];
public function prepareForValidation($attributes) {
$attributes['computed'] = $this->getComputedProperty();
return $attributes;
}
public function getComputedProperty() {
return $this->helper;
}
public function getFailProperty() {
return 'I will fail';
}
public function mount($helper = null)
{
$this->helper = $helper;
}
public function runValidationRuleWithoutProperty()
{
$this->rules['fail'] = 'require|min:1';
$this->validate();
}
public function runValidation()
{
$this->validate();
}
public function runResetValidation()
{
$this->resetValidation();
}
}
class ValidatesWireableProperty extends TestComponent
{
public CustomWireableValidationCollection $customCollection;
public $rules = [
'customCollection.*.amount' => 'required|gt:100'
];
public function mount()
{
$this->customCollection = new CustomWireableValidationCollection([
new CustomWireableValidationDTO(50),
]);
}
public function runValidation()
{
$this->validate();
}
}
class CustomWireableValidationCollection extends Collection implements Wireable
{
public function toLivewire()
{
return $this->mapWithKeys(function($dto, $key) {
return [$key => $dto instanceof CustomWireableValidationDTO ? $dto->toLivewire() : $dto];
})->all();
}
public static function fromLivewire($value)
{
return static::wrap($value)
->mapWithKeys(function ($dto, $key) {
return [$key => CustomWireableValidationDTO::fromLivewire($dto)];
});
}
}
class CustomWireableValidationDTO implements Wireable
{
public $amount;
public function __construct($amount)
{
$this->amount = $amount;
}
public function toLivewire()
{
return [
'amount' => $this->amount
];
}
public static function fromLivewire($value)
{
return new static(
$value['amount']
);
}
}
class AddErrorInMount extends Component
{
public function mount()
{
$this->addErrors();
}
public function addErrors(): void
{
$this->addError('first', 'first error');
$this->addError('second', 'second error');
$this->addError('third', 'third error');
}
public function addFilterErrors(): void
{
$this->resetErrorBag();
$this->addError('first', 'first error');
$this->addError('second', 'second error');
}
public function render()
{
return view('show-errors');
}
}
?>
Did this file decode correctly?
Original Code
<?php
namespace Livewire\Features\SupportValidation;
use Tests\TestComponent;
use Livewire\Wireable;
use Livewire\Livewire;
use Livewire\Exceptions\MissingRulesException;
use Livewire\Component;
use Livewire\Attributes\Validate;
use Livewire\Attributes\Rule;
use Illuminate\Support\ViewErrorBag;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Collection;
class UnitTest extends \Tests\TestCase
{
public function test_update_triggers_rule_attribute()
{
Livewire::test(new class extends TestComponent {
#[Validate('required')]
public $foo = '';
#[Validate('required')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
]);
}
public function test_deprecated_rule_alias_can_be_used()
{
Livewire::test(new class extends TestComponent {
#[Rule('required')]
public $foo = '';
#[Rule('required')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
]);
}
public function test_validate_can_be_used_without_a_rule()
{
Livewire::test(new class extends TestComponent {
#[Validate]
public $foo = '';
public $bar = '';
public function rules()
{
return [
'foo' => 'required',
'bar' => 'required',
];
}
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'testing...')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required']);
}
public function test_realtime_validation_can_be_opted_out_of()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', onUpdate: false)]
public $foo = '';
#[Validate('required|min:3')]
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->assertHasNoErrors()
->set('bar', 'te')
->assertHasErrors()
->set('bar', 'testing...')
->assertHasNoErrors()
->set('foo', 'te')
->assertHasNoErrors()
->call('save')
->assertHasErrors();
}
public function test_rule_attribute_supports_custom_attribute()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', attribute: 'The Foo')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The The Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_attribute_as_alias()
{
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'The Foo')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The The Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_is_translatable()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'translatable.foo')]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The Translated Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_is_translatable_with_array()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: ['foo' => 'translatable.foo'])]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The Translated Foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_alias_translation_can_be_opted_out()
{
Lang::addLines(['translatable.foo' => 'Translated Foo'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('required|min:3', as: 'translatable.foo', translate: false)]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('The translatable.foo field must be at least 3 characters.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_messages()
{
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: 'Your foo is too short.')]
public $foo = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_supports_custom_messages_when_using_repeated_attributes()
{
Livewire::test(new class extends TestComponent {
#[Validate('required', message: 'Please provide a post title')]
#[Validate('min:3', message: 'This title is too short')]
public $title = '';
})
->set('title', '')
->assertHasErrors(['title' => 'required'])
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Please provide a post title', $messages['title'][0]);
})
;
}
public function test_rule_attribute_message_is_translatable()
{
Lang::addLines(['translatable.foo' => 'Your foo is too short.'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: 'translatable.foo')]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attribute_message_is_translatable_with_array()
{
Lang::addLines(['translatable.foo' => 'Your foo is too short.'], App::currentLocale());
Livewire::test(new class extends TestComponent {
#[Validate('min:5', message: ['min' => 'translatable.foo'])]
public $foo = '';
})
->set('foo', 'te')
->assertHasErrors()
->tap(function ($component) {
$messages = $component->errors()->getMessages();
$this->assertEquals('Your foo is too short.', $messages['foo'][0]);
})
;
}
public function test_rule_attributes_can_contain_rules_for_multiple_properties()
{
Livewire::test(new class extends TestComponent {
#[Validate(['foo' => 'required', 'bar' => 'required'])]
public $foo = '';
public $bar = '';
function clear() { $this->clearValidation(); }
function save() { $this->validate(); }
})
->set('bar', '')
->assertHasNoErrors()
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->call('clear')
->assertHasNoErrors()
->call('save')
->assertHasErrors([
'foo' => 'required',
'bar' => 'required',
]);
}
public function test_rule_attributes_can_contain_multiple_rules()
{
Livewire::test(new class extends TestComponent {
#[Validate(['required', 'min:2', 'max:3'])]
public $foo = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
;
}
public function test_rule_attributes_can_contain_multiple_rules_userland(): void
{
Livewire::test(new class extends TestComponent {
#[\Livewire\Attributes\Validate('required')]
#[\Livewire\Attributes\Validate('min:2')]
#[\Livewire\Attributes\Validate('max:3')]
public $foo = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
;
}
public function test_rule_attributes_can_be_repeated()
{
Livewire::test(new class extends TestComponent {
#[Validate('required')]
#[Validate('min:2')]
#[Validate('max:3')]
public $foo = '';
#[
Validate('sometimes'),
Validate('max:1')
]
public $bar = '';
})
->set('foo', '')
->assertHasErrors(['foo' => 'required'])
->set('foo', '1')
->assertHasErrors(['foo' => 'min'])
->set('foo', '12345')
->assertHasErrors(['foo' => 'max'])
->set('foo', 'ok')
->assertHasNoErrors()
->set('bar', '12')
->assertHasErrors(['bar' => 'max'])
->set('bar', '1')
->assertHasNoErrors()
;
}
public function test_validate_with_rules_property()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('foo', '')
->call('save')
->assertHasErrors(['foo' => 'required']);
}
public function test_validate_only_with_rules_property()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('bar', '')
->assertHasErrors(['bar' => 'required']);
}
public function test_validate_without_rules_property_and_no_args_throws_exception()
{
$this->expectException(MissingRulesException::class);
Livewire::test(ComponentWithoutRulesProperty::class)->call('save');
}
public function test_can_validate_collection_properties()
{
Livewire::test(ComponentWithRulesProperty::class)
->set('foo', 'filled')
->call('save')
->assertHasErrors('baz.*.foo')
->set('baz.0.foo', 123)
->set('baz.1.foo', 456)
->call('save')
->assertHasNoErrors('baz.*.foo');
}
public function test_validate_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidation');
$this->assertStringNotContainsString('The foo field is required', $component->html());
$this->assertStringContainsString('The bar field is required', $component->html());
}
public function test_validate_component_properties_with_custom_message()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomMessage');
$this->assertStringContainsString('Custom Message', $component->html());
}
public function test_validate_component_properties_with_custom_message_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithMessageProperty');
$this->assertStringContainsString('Custom Message', $component->html());
}
public function test_validate_component_properties_with_custom_attribute_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithAttributesProperty');
$this->assertStringContainsString('The foobar field is required.', $component->html());
$this->assertStringContainsString('The Items Baz field is required.', $component->html());
}
public function test_validate_component_properties_with_custom_attribute()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomAttribute');
$this->assertStringContainsString('The foobar field is required.', $component->html());
}
public function test_validate_component_properties_with_custom_value_property()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runValidationWithCustomValuesProperty');
$this->assertStringContainsString('The bar field is required when foo is my custom value.', $component->html());
}
public function test_validate_nested_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runNestedValidation');
$this->assertStringContainsString('The emails.1 field must be a valid email address.', $component->effects['html']);
}
public function test_validate_deeply_nested_component_properties()
{
$component = Livewire::test(ForValidation::class);
$component->runAction('runDeeplyNestedValidation');
$this->assertStringContainsString('items.1.baz field is required', $component->html());
$this->assertStringNotContainsString('items.0.baz field is required', $component->html());
}
public function test_validation_errors_persist_across_requests()
{
$component = Livewire::test(ForValidation::class);
$component->call('runValidation')
->assertSee('The bar field is required')
->set('foo', 'bar')
->assertSee('The bar field is required');
}
public function test_old_validation_errors_are_overwritten_if_new_request_has_errors()
{
$component = Livewire::test(ForValidation::class);
$component->call('runValidation')
->set('foo', '')
->call('runValidation')
->call('$refresh')
->assertSee('The foo field is required');
}
public function test_old_validation_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', '')
->set('bar', '')
->call('runValidation')
->assertSee('The foo field is required')
->assertSee('The bar field is required')
->set('foo', 'foo')
->set('bar', 'bar')
->call('runValidation')
->assertDontSee('The foo field is required')
->assertDontSee('The bar field is required');
}
public function test_can_validate_only_a_specific_field_and_preserve_other_validation_messages()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', '')
->call('runValidation')
->assertDontSee('The foo field is required')
->assertSee('The bar field is required')
->set('foo', '')
->call('runValidationOnly', 'foo')
->assertSee('The foo field is required')
->assertSee('The bar field is required');
}
public function test_validating_only_a_specific_field_wont_throw_an_error_if_the_field_doesnt_exist()
{
$component = Livewire::test(ForValidation::class);
$component
->set('bar', '')
->call('runValidationOnlyWithFooRules', 'bar')
->assertDontSee('The foo field is required')
->assertDontSee('The bar field is required');
}
public function test_validating_only_a_specific_field_wont_throw_an_error_if_the_array_key_doesnt_exist()
{
$component = Livewire::test(ForValidation::class);
$component
->set('items', [])
->call('runDeeplyNestedValidationOnly', 'items.0.baz')
->assertSee('items.0.baz field is required');
}
public function test_can_validate_only_a_specific_field_with_custom_message_property()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', '')
->call('runValidationOnlyWithMessageProperty', 'foo')
->assertDontSee('Foo Message') // Foo is set, no error
->assertDontSee('Bar Message') // Bar is not being validated, don't show
->set('foo', '')
->call('runValidationOnlyWithMessageProperty', 'bar')
->assertDontSee('Foo Message') // Foo is not being validated, don't show
->assertSee('Bar Message'); // Bar is not set, show message
}
public function test_can_validate_only_a_specific_field_with_custom_attributes_property()
{
$component = Livewire::test(ForValidation::class);
$component
->call('runValidationOnlyWithAttributesProperty', 'bar')
->assertSee('The foobar field is required.')
->call('runValidationOnlyWithAttributesProperty', 'items.*.baz') // Test wildcard works
->assertSee('The Items Baz field is required.')
->call('runValidationOnlyWithAttributesProperty', 'items.1.baz') // Test specific instance works
->assertSee('The Items Baz field is required.')
;
}
public function test_can_validate_only_a_specific_field_with_deeply_nested_array()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.0.baz')
->assertDontSee('items.0.baz field is required')
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required');
}
public function test_old_deeply_nested_wildcard_validation_only_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required');
}
public function test_old_deeply_nested_wildcard_validation_only_is_cleared_if_new_validation_fails()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->set('items.0.baz', '')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required')
->assertSee('items.0.baz field is required');
}
public function test_old_deeply_nested_specific_validation_only_is_cleared_if_new_validation_passes()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertDontSee('items.1.baz field is required');
}
public function test_old_deeply_nested_specific_validation_only_is_cleared_if_new_validation_fails()
{
$component = Livewire::test(ForValidation::class);
$component
->runAction('runDeeplyNestedValidationOnly', 'items.1.baz')
->assertSee('items.1.baz field is required')
->set('items.1.baz', 'blab')
->set('items.0.baz', '')
->runAction('runDeeplyNestedValidationOnly', 'items.*.baz')
->assertDontSee('items.1.baz field is required')
->assertSee('items.0.baz field is required');
}
public function test_validation_errors_are_shared_for_all_views()
{
$component = Livewire::test(ForValidation::class);
app('view')->share('errors', $errors = new ViewErrorBag);
$component
->set('bar', '')
->call('runValidation')
->assertSee('sharedError:The bar field is required');
$this->assertTrue(app('view')->shared('errors') === $errors);
}
public function test_validation_errors_are_shared_when_redirecting_back_to_full_page_component()
{
// We apply the web middleware here so that the errors will get shared
// from the session to the view via ShareErrorsFromSession middleware
Route::get('/full-page-component', ForValidation::class)->middleware('web');
Route::post('/non-livewire-form', function () {
request()->validate(['bar' => 'required']);
});
$post = $this
->from('/full-page-component')
->followingRedirects()
->post(url('/non-livewire-form'))
->assertSee('sharedError:The bar field is required');
}
public function test_multi_word_validation_rules_failing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'bar123&*(O)')
->call('runValidationWithMultiWordRule')
->assertHasErrors(['foo' => 'alpha_dash']);
}
public function test_class_based_validation_rules_failing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'barbaz')
->call('runValidationWithClassBasedRule')
->assertHasErrors(['foo' => ValueEqualsFoobar::class]);
}
public function test_can_assert_has_no_errors_when_no_validation_has_failed_and_specific_keys_are_supplied()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo')
->set('bar', 'bar')
->call('runValidation')
->assertHasNoErrors(['foo' => 'required']);
}
public function test_multi_word_validation_rules_passing_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foo-bar-baz')
->call('runValidationWithMultiWordRule')
->assertHasNoErrors(['foo' => 'alpha_dash']);
}
public function test_class_based_validation_rules_are_assertable()
{
$component = Livewire::test(ForValidation::class);
$component
->set('foo', 'foobar')
->call('runValidationWithClassBasedRule')
->assertHasNoErrors(['foo' => ValueEqualsFoobar::class]);
}
public function test_custom_validation_messages_are_cleared_between_validate_only_validations()
{
$component = Livewire::test(ForValidation::class);
// cleared when custom validation passes
$component
->set('foo', 'foo')
->set('bar', 'b')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertSee('Lengths must be the same')
->set('bar', 'baz')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertDontSee('Lengths must be the same');
// cleared when custom validation isn't run
$component
->set('foo', 'foo')
->set('bar', 'b')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertDontSee('The bar field is required')
->assertSee('Lengths must be the same')
->set('bar', '')
->call('runValidationOnlyWithCustomValidation', 'bar')
->assertSee('The bar field is required')
->assertDontSee('Lengths must be the same');
}
public function test_validation_fails_when_same_rule_is_used_without_matching()
{
Livewire::test(ForValidation::class)
->set('password', 'supersecret')
->call('runSameValidation')
->assertSee('The password field must match password confirmation.');
}
public function test_validation_passes_when_same_rule_is_used_and_matches()
{
Livewire::test(ForValidation::class)
->set('password', 'supersecret')
->set('passwordConfirmation', 'supersecret')
->call('runSameValidation')
->assertDontSee('The password field must match password confirmation.');
}
public function test_only_data_in_validation_rules_is_returned()
{
$component = new ForValidation();
$component->bar = 'is required';
$validatedData = $component->runValidationWithoutAllPublicPropertiesAndReturnValidatedData();
$this->assertSame([
'bar' => $component->bar,
], $validatedData);
}
public function test_can_assert_validation_errors_on_errors_thrown_from_custom_validator()
{
$component = Livewire::test(ForValidation::class);
$component->call('failFooOnCustomValidator')->assertHasErrors('plop');
}
public function test_can_use_withvalidator_method()
{
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidationWithClosure')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidationWithThisMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidateOnlyWithClosure')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('runValidateOnlyWithThisMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('clearWithValidatorAfterRunningValidateMethod')->assertSetStrict('count', 1);
$component = Livewire::test(WithValidationMethod::class);
$component->assertSetStrict('count', 0)->call('clearWithValidatorAfterRunningValidateOnlyMethod')->assertSetStrict('count', 1);
}
public function test_a_set_of_items_will_validate_individually()
{
Livewire::test(ValidatesOnlyTestComponent::class, ['image' => 'image', 'imageAlt' => 'This is an image'])
->call('runValidateOnly', 'image_alt')
->assertHasNoErrors(['image_alt', 'image_url', 'image'])
->call('runValidateOnly', 'image_url')
->assertHasNoErrors(['image', 'image_url', 'image_alt'])
->call('runValidateOnly', 'image')
->assertHasNoErrors(['image', 'image_url', 'image_alt']);
}
public function test_a_computed_property_is_able_to_validate()
{
Livewire::test(ValidatesComputedProperty::class, ['helper' => 10])
->call('runValidation')
->assertHasNoErrors(['computed'])
->set('helper', -1)
->call('runValidation')
->assertHasErrors(['computed']);
$this->expectExceptionMessage('No property found for validation: [missing]');
Livewire::test(ForValidation::class)
->call('runValidationOnlyWithMissingProperty', 'missing');
$this->expectExceptionMessage('No property found for validation: [fail]');
Livewire::test(ValidatesComputedProperty::class)
->call('runValidationRuleWithoutProperty');
}
public function test_when_unwrapping_data_for_validation_an_object_is_checked_if_it_is_wireable_first()
{
$this->markTestSkipped('Not sure we support setting data on a wireable without requiring a ->set method on the wireable...');
Livewire::test(ValidatesWireableProperty::class)
->call('runValidation')
->assertHasErrors('customCollection.0.amount')
->set('customCollection.0.amount', 150)
->call('runValidation')
->assertHasNoErrors('customCollection.0.amount')
;
}
public function test_adding_validation_error_inside_mount_method()
{
Livewire::test(AddErrorInMount::class)
->call('addErrors')
->assertSee('first error')
->assertSee('second error')
->assertSee('third error')
->assertHasErrors(['first', 'second', 'third'])
->call('addFilterErrors')
->assertSee('first error')
->assertSee('second error')
->assertHasErrors(['first', 'second']);
Livewire::test(AddErrorInMount::class)
->assertSee('first error')
->assertSee('second error')
->assertSee('third error')
->assertHasErrors(['first', 'second', 'third'])
->call('addFilterErrors')
->assertSee('first error')
->assertSee('second error')
->assertHasErrors(['first', 'second']);
}
}
class ComponentWithRulesProperty extends TestComponent
{
public $foo;
public $bar = 'baz';
public $baz;
protected $rules = [
'foo' => 'required',
'bar' => 'required',
'baz.*.foo' => 'numeric',
];
public function mount()
{
$this->baz = collect([
['foo' => 'a'],
['foo' => 'b'],
]);
}
public function updatedBar()
{
$this->validateOnly('bar');
}
public function save()
{
$this->validate();
}
}
class ComponentWithoutRulesProperty extends TestComponent
{
public $foo;
public function save()
{
$this->validate();
}
}
class ForValidation extends Component
{
public $foo = 'foo';
public $bar = '';
public $emails = ['[email protected]', 'invalid-email'];
public $items = [
['foo' => 'bar', 'baz' => 'blab'],
['foo' => 'bar', 'baz' => ''],
];
public $password = '';
public $passwordConfirmation = '';
public $messages = [];
public $validationAttributes = [];
public $validationCustomValues = [];
public function runValidation()
{
$this->validate([
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationWithMultiWordRule()
{
$this->validate([
'foo' => 'alpha_dash',
]);
}
public function runValidationWithClassBasedRule()
{
$this->validate([
'foo' => [new ValueEqualsFoobar],
]);
}
public function runValidationOnly($field)
{
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationOnlyWithMissingProperty($field)
{
$this->validateOnly($field, [
'missing' => 'required',
]);
}
public function runValidationOnlyWithFooRules($field)
{
$this->validateOnly($field, [
'foo' => 'required',
]);
}
public function runValidationOnlyWithCustomValidation($field)
{
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
Validator::make(
[
'foo_length' => strlen($this->foo),
'bar_length' => strlen($this->bar),
],
[ 'foo_length' => 'same:bar_length' ],
[ 'same' => 'Lengths must be the same' ]
)->validate();
}
public function runValidationOnlyWithMessageProperty($field)
{
$this->messages = [
'foo.required' => 'Foo Message',
'bar.required' => 'Bar Message',
];
$this->validateOnly($field, [
'foo' => 'required',
'bar' => 'required',
]);
}
public function runValidationOnlyWithAttributesProperty($field)
{
$this->validationAttributes = [
'bar' => 'foobar',
'items.*.baz' => 'Items Baz',
];
$this->validateOnly($field, [
'bar' => 'required',
'items.*.baz' => 'required',
]);
}
public function runDeeplyNestedValidationOnly($field)
{
$this->validateOnly($field, [
'items' => ['required', 'array'],
'items.*' => 'array',
'items.*.foo' => ['required', 'string'],
'items.*.baz' => ['required', 'string'],
]);
}
public function runValidationWithCustomMessage()
{
$this->validate([
'bar' => 'required',
], ['required' => 'Custom Message']);
}
public function runValidationWithMessageProperty()
{
$this->messages = [
'required' => 'Custom Message'
];
$this->validate([
'bar' => 'required'
]);
}
public function runValidationWithAttributesProperty()
{
$this->validationAttributes = [
'bar' => 'foobar',
'items.*.baz' => 'Items Baz'
];
$this->validate([
'bar' => 'required',
'items.*.baz' => 'required',
]);
}
public function runValidationWithCustomAttribute()
{
$this->validate([
'bar' => 'required',
], [], ['bar' => 'foobar']);
}
public function runValidationWithCustomValuesProperty()
{
$this->foo = 'my';
$this->validationCustomValues = [
'foo' => [
'my' => 'my custom value',
],
];
$this->validate([
'bar' => 'required_if:foo,my',
]);
}
public function runNestedValidation()
{
$this->validate([
'emails.*' => 'email',
]);
}
public function runDeeplyNestedValidation()
{
$this->validate([
'items' => ['required', 'array'],
'items.*' => 'array',
'items.*.foo' => ['required', 'string'],
'items.*.baz' => ['required', 'string'],
]);
}
public function runSameValidation()
{
$this->validate([
'password' => 'same:passwordConfirmation',
]);
}
public function runValidationWithoutAllPublicPropertiesAndReturnValidatedData()
{
return $this->validate(['bar' => 'required']);
}
public function failFooOnCustomValidator()
{
Validator::make([], ['plop' => 'required'])->validate();
}
public function render()
{
return app('view')->make('dump-errors');
}
}
class ValueEqualsFoobar implements \Illuminate\Contracts\Validation\Rule
{
public function passes($attribute, $value)
{
return $value === 'foobar';
}
public function message()
{
return '';
}
}
class WithValidationMethod extends Component
{
public $foo = 'bar';
public $count = 0;
public function runValidationWithClosure()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validate([
'foo' => 'required',
]);
}
public function runValidateOnlyWithClosure()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validateOnly('foo', [
'foo' => 'required',
]);
}
public function runValidationWithThisMethod()
{
$this->withValidator([$this, 'doSomethingWithValidator'])->validate([
'foo' => 'required',
]);
}
public function runValidateOnlyWithThisMethod()
{
$this->withValidator([$this, 'doSomethingWithValidator'])->validateOnly('foo', [
'foo' => 'required',
]);
}
public function clearWithValidatorAfterRunningValidateMethod()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validate([
'foo' => 'required',
]);
$this->validate(['foo' => 'required']);
}
public function clearWithValidatorAfterRunningValidateOnlyMethod()
{
$this->withValidator(function ($validator) {
$validator->after(function ($validator) {
$this->count++;
});
})->validateOnly('foo', [
'foo' => 'required',
]);
$this->validateOnly('foo', ['foo' => 'required']);
}
protected function doSomethingWithValidator($validator)
{
$validator->after(function ($validator) {
$this->count++;
});
}
public function render()
{
return app('view')->make('dump-errors');
}
}
class ValidatesOnlyTestComponent extends TestComponent
{
public $image = '';
public $image_alt = '';
public $image_url = '';
public $rules = [
'image' => 'required_without:image_url|string',
'image_alt' => 'required|string',
'image_url' => 'required_without:image|string'
];
public function mount($image, $imageAlt, $imageUrl = '')
{
$this->image = $image;
$this->image_alt = $imageAlt;
$this->image_url = $imageUrl;
}
public function runValidation()
{
$this->validate();
}
public function runValidateOnly($propertyName)
{
$this->validateOnly($propertyName);
}
public function runResetValidation()
{
$this->resetValidation();
}
}
class ValidatesComputedProperty extends TestComponent
{
public $helper;
public $rules = [
'computed' => 'required|gte:0'
];
public function prepareForValidation($attributes) {
$attributes['computed'] = $this->getComputedProperty();
return $attributes;
}
public function getComputedProperty() {
return $this->helper;
}
public function getFailProperty() {
return 'I will fail';
}
public function mount($helper = null)
{
$this->helper = $helper;
}
public function runValidationRuleWithoutProperty()
{
$this->rules['fail'] = 'require|min:1';
$this->validate();
}
public function runValidation()
{
$this->validate();
}
public function runResetValidation()
{
$this->resetValidation();
}
}
class ValidatesWireableProperty extends TestComponent
{
public CustomWireableValidationCollection $customCollection;
public $rules = [
'customCollection.*.amount' => 'required|gt:100'
];
public function mount()
{
$this->customCollection = new CustomWireableValidationCollection([
new CustomWireableValidationDTO(50),
]);
}
public function runValidation()
{
$this->validate();
}
}
class CustomWireableValidationCollection extends Collection implements Wireable
{
public function toLivewire()
{
return $this->mapWithKeys(function($dto, $key) {
return [$key => $dto instanceof CustomWireableValidationDTO ? $dto->toLivewire() : $dto];
})->all();
}
public static function fromLivewire($value)
{
return static::wrap($value)
->mapWithKeys(function ($dto, $key) {
return [$key => CustomWireableValidationDTO::fromLivewire($dto)];
});
}
}
class CustomWireableValidationDTO implements Wireable
{
public $amount;
public function __construct($amount)
{
$this->amount = $amount;
}
public function toLivewire()
{
return [
'amount' => $this->amount
];
}
public static function fromLivewire($value)
{
return new static(
$value['amount']
);
}
}
class AddErrorInMount extends Component
{
public function mount()
{
$this->addErrors();
}
public function addErrors(): void
{
$this->addError('first', 'first error');
$this->addError('second', 'second error');
$this->addError('third', 'third error');
}
public function addFilterErrors(): void
{
$this->resetErrorBag();
$this->addError('first', 'first error');
$this->addError('second', 'second error');
}
public function render()
{
return view('show-errors');
}
}
Function Calls
None |
Stats
MD5 | 0ce6cbed552108333e9d5653e0408015 |
Eval Count | 0 |
Decode Time | 108 ms |