diff --git a/README.md b/README.md index f84c1ec..8dd2271 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,7 @@ There are common predefined validators, but you can implement custom validators #### FormControl - Validators.required +- Validators.requiredNonEmpty - Validators.requiredTrue - Validators.email - Validators.number diff --git a/lib/src/validators/required_non_empty_validator.dart b/lib/src/validators/required_non_empty_validator.dart new file mode 100644 index 0000000..d1f0fa1 --- /dev/null +++ b/lib/src/validators/required_non_empty_validator.dart @@ -0,0 +1,29 @@ +// Copyright 2020 Joan Pablo Jimenez Milian. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +import 'package:reactive_forms/reactive_forms.dart'; + +/// Validator that requires the control's value to not be null or empty. +class RequiredNonEmptyValidator extends Validator { + const RequiredNonEmptyValidator() : super(); + @override + Map? validate(AbstractControl control) { + final error = {ValidationMessage.requiredNonEmpty: true}; + final dynamic value = control.value; + + if (value == null) { + return error; + } else if (value is String) { + return value.trim().isEmpty ? error : null; + } else if (value is Iterable && value.isEmpty) { + return error; + } else if (value is Map && value.isEmpty) { + return error; + } else if (value is Set && value.isEmpty) { + return error; + } + + return null; + } +} diff --git a/lib/src/validators/required_validator.dart b/lib/src/validators/required_validator.dart index 13f3367..80c613b 100644 --- a/lib/src/validators/required_validator.dart +++ b/lib/src/validators/required_validator.dart @@ -4,7 +4,7 @@ import 'package:reactive_forms/reactive_forms.dart'; -/// Validator that requires the control have a non-empty value. +/// Validator that requires the control's value to not be null or blank string. class RequiredValidator extends Validator { const RequiredValidator() : super(); diff --git a/lib/src/validators/validation_message.dart b/lib/src/validators/validation_message.dart index e91c2ed..736a484 100644 --- a/lib/src/validators/validation_message.dart +++ b/lib/src/validators/validation_message.dart @@ -20,6 +20,9 @@ class ValidationMessage { /// Key text for required validation message. static const String required = 'required'; + /// Key text for `requiredNonEmpty` validation message. + static const String requiredNonEmpty = 'requiredNonEmpty'; + /// Key text for pattern validation message. static const String pattern = 'pattern'; diff --git a/lib/src/validators/validators.dart b/lib/src/validators/validators.dart index fb02a50..cec22f2 100644 --- a/lib/src/validators/validators.dart +++ b/lib/src/validators/validators.dart @@ -23,6 +23,7 @@ import 'package:reactive_forms/src/validators/pattern/default_pattern_evaluator. import 'package:reactive_forms/src/validators/pattern/pattern_evaluator.dart'; import 'package:reactive_forms/src/validators/pattern/regex_pattern_evaluator.dart'; import 'package:reactive_forms/src/validators/pattern_validator.dart'; +import 'package:reactive_forms/src/validators/required_non_empty_validator.dart'; import 'package:reactive_forms/src/validators/required_validator.dart'; /// Provides a set of built-in validators that can be used by form controls, @@ -39,9 +40,16 @@ class Validators { AsyncValidatorFunction validator) => DelegateAsyncValidator(validator); - /// Gets a validator that requires the control have a non-empty value. + /// Gets a validator that requires the control's value to not be null or + /// blank string. static Validator get required => const RequiredValidator(); + /// Gets a validator that requires the control's value be non-empty. + /// This validator is commonly used for fields that are both required and + /// should have a non-empty value. + static Validator get requiredNonEmpty => + const RequiredNonEmptyValidator(); + /// Gets a validator that requires the control's value be true. /// This validator is commonly used for required checkboxes. static Validator get requiredTrue => const EqualsValidator( diff --git a/test/src/validators/required_non_empty_validator_test.dart b/test/src/validators/required_non_empty_validator_test.dart new file mode 100644 index 0000000..7e29894 --- /dev/null +++ b/test/src/validators/required_non_empty_validator_test.dart @@ -0,0 +1,110 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:reactive_forms/reactive_forms.dart'; + +void main() { + group('Required Non Empty validator tests', () { + test('FormControl is invalid if value is null', () { + // Given: an invalid control + final control = FormControl( + value: null, + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is invalid + expect(control.valid, false); + expect(control.errors, {ValidationMessage.requiredNonEmpty: true}); + }); + + test('FormControl is valid if value is not null', () { + // Given: a valid control + final control = FormControl( + value: 42, + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is valid + expect(control.valid, true); + }); + + test('FormControl is invalid if string value is null', () { + // Given: an invalid control + final control = FormControl( + value: null, + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is invalid + expect(control.valid, false); + expect(control.errors, {ValidationMessage.requiredNonEmpty: true}); + }); + + test('FormControl is invalid if string value is empty', () { + // Given: an invalid control + final control = FormControl( + value: " ", + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is invalid + expect(control.valid, false); + expect(control.errors, {ValidationMessage.requiredNonEmpty: true}); + }); + + test('FormControl is valid if string value is not null', () { + // Given: a valid control + final control = FormControl( + value: "42", + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is valid + expect(control.valid, true); + }); + + test('FormControl is invalid if value is an emtpy list', () { + // Given: a valid control + final control = FormControl>( + value: [], + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is invalid + expect(control.valid, false); + expect(control.errors, {ValidationMessage.requiredNonEmpty: true}); + }); + + test('FormControl is valid if value is non emtpy list', () { + // Given: a valid control + final control = FormControl>( + value: [1], + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is valid + expect(control.valid, true); + }); + + test('FormControl is invalid if value is an emtpy map', () { + // Given: a valid control + final control = FormControl>( + value: {}, + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is invalid + expect(control.valid, false); + expect(control.errors, {ValidationMessage.requiredNonEmpty: true}); + }); + + test('FormControl is valid if value is non emtpy map', () { + // Given: a valid control + final control = FormControl>( + value: {"answer": 42}, + validators: [Validators.requiredNonEmpty], + ); + + // Expect: control is valid + expect(control.valid, true); + }); + }); +}