Skip to content

Commit 8fab069

Browse files
implement or composition validator
1 parent 3024e98 commit 8fab069

File tree

2 files changed

+134
-5
lines changed

2 files changed

+134
-5
lines changed

lib/new_api_prototype/core_validators/composition_validators.dart

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import '../constants.dart';
55
// Something like: FormBuilderLocalizations.current.andSeparator;
66
const String andSeparatorTemporary = ' and ';
77

8+
// TODO (ArturAssisComp): add the translated 'or' separator error message.
9+
// Something like: FormBuilderLocalizations.current.orSeparator;
10+
const String orSeparatorTemporary = ' or ';
11+
812
/// This function returns a validator that is an AND composition of `validators`.
913
/// The composition is done following the order of validators from `validators`.
1014
/// If `printErrorAsSoonAsPossible` is true, it prints the error of the first
@@ -47,16 +51,36 @@ Validator<T> and<T extends Object>(
4751
};
4852
}
4953

50-
Validator<T> or<T extends Object>(List<Validator<T>> validators) {
54+
/// This function returns a validator that is an OR composition of `validators`.
55+
/// The composition is done following the order of validators from `validators`.
56+
/// The validator returns null as soon as a validator from the composition
57+
/// returns null. Otherwise, the failure message will be composed in the
58+
/// following way:
59+
/// '$prefix<msg1>$separator<msg2>$separator<msg3>...$separator<msgN>$suffix'.
60+
///
61+
/// # Parameters
62+
/// - String? `separator`: the separator of each validator failure message when
63+
/// all validators fail. By default, it is
64+
/// `FormBuilderLocalizations.current.orSeparator`,
65+
///
66+
/// # Errors
67+
/// - Throws [AssertionError] if `validators` is empty.
68+
Validator<T> or<T extends Object>(
69+
List<Validator<T>> validators, {
70+
String prefix = '',
71+
String suffix = '',
72+
String? separator,
73+
}) {
74+
assert(validators.isNotEmpty, 'The input validators may not be empty.');
5175
return (T value) {
52-
final errorMessageBuilder = <String>[];
53-
for (final validator in validators) {
54-
final errorMessage = validator(value);
76+
final List<String> errorMessageBuilder = <String>[];
77+
for (final Validator<T> validator in validators) {
78+
final String? errorMessage = validator(value);
5579
if (errorMessage == null) {
5680
return null;
5781
}
5882
errorMessageBuilder.add(errorMessage);
5983
}
60-
return errorMessageBuilder.join(' OR ');
84+
return '$prefix${errorMessageBuilder.join(separator ?? orSeparatorTemporary)}$suffix';
6185
};
6286
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:form_builder_validators/form_builder_validators.dart';
3+
4+
const String errorGt = 'error gt';
5+
Validator<num> gt(num target) {
6+
return (num v) {
7+
return v > target ? null : errorGt;
8+
};
9+
}
10+
11+
const String errorLtE = 'error lte';
12+
Validator<num> ltE(num target) {
13+
return (num v) {
14+
return v <= target ? null : errorLtE;
15+
};
16+
}
17+
18+
const String errorIsEven = 'error is even';
19+
Validator<num> isEven() {
20+
return (num v) {
21+
if (v is int && v % 2 == 0) {
22+
return null;
23+
}
24+
return errorIsEven;
25+
};
26+
}
27+
28+
void main() {
29+
group('Composition: or', () {
30+
test('Should validate using only one validator', () {
31+
final Validator<num> v = or(<Validator<num>>[gt(10)]);
32+
33+
expect(v(9), errorGt);
34+
expect(v(10), errorGt);
35+
expect(v(11), isNull);
36+
});
37+
38+
test('Should validate using two validators', () {
39+
final Validator<num> v1 = or(<Validator<num>>[gt(100), ltE(19)]);
40+
final Validator<num> v2 = or(<Validator<num>>[ltE(19), gt(100)]);
41+
42+
expect(v1(9), isNull);
43+
expect(v1(23), '$errorGt$orSeparatorTemporary$errorLtE');
44+
expect(v1(1002), isNull);
45+
46+
expect(v2(19), isNull);
47+
expect(v2(100), '$errorLtE$orSeparatorTemporary$errorGt');
48+
expect(v2(101), isNull);
49+
});
50+
51+
test(
52+
'Should validate if the input is even or a number greater than 10.4 or less than or equal to 9.0',
53+
() {
54+
final Validator<num> v1 =
55+
or(<Validator<num>>[isEven(), gt(10.4), ltE(9.0)]);
56+
final Validator<num> v2 =
57+
or(<Validator<num>>[ltE(9.0), gt(10.4), isEven()]);
58+
59+
expect(v1(3), isNull);
60+
expect(v1(10), isNull);
61+
expect(v1(10.1),
62+
'$errorIsEven$orSeparatorTemporary$errorGt$orSeparatorTemporary$errorLtE');
63+
expect(v1(13), isNull);
64+
65+
expect(v2(3), isNull);
66+
expect(v2(10), isNull);
67+
expect(v2(10.1),
68+
'$errorLtE$orSeparatorTemporary$errorGt$orSeparatorTemporary$errorIsEven');
69+
expect(v2(13), isNull);
70+
});
71+
72+
test(
73+
'Should validate if the input is even or greater than 5 or divisible by 37 with custom prefix, suffix and separator.',
74+
() {
75+
const String prefix = 'prefix';
76+
const String suffix = 'suffix';
77+
const String separator = ' oR ';
78+
const String errorDivBy37 = 'not divisible by 37';
79+
final Validator<int> v = or(
80+
<Validator<int>>[
81+
isEven(),
82+
gt(5),
83+
(int v) => v % 37 == 0 ? null : errorDivBy37
84+
],
85+
prefix: prefix,
86+
suffix: suffix,
87+
separator: separator,
88+
);
89+
90+
expect(
91+
v(1),
92+
equals('$prefix${[
93+
errorIsEven,
94+
errorGt,
95+
errorDivBy37
96+
].join(separator)}$suffix'));
97+
expect(v(2), isNull);
98+
expect(v(7), isNull);
99+
expect(v(74), isNull);
100+
});
101+
test('Should throw AssertionError when the validators input is empty', () {
102+
expect(() => or(<Validator<Object?>>[]), throwsAssertionError);
103+
});
104+
});
105+
}

0 commit comments

Comments
 (0)