Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#63 - Specialised int validators #66

Merged
merged 12 commits into from
Oct 7, 2024
1 change: 1 addition & 0 deletions glade_forms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## DEV:
- **[Add]**: Add `IntInput` as a specialized variant of GladeInput<int> which has additional, int related, validations such as `isBetween`, `isMin`, `isMax`
- **[Add]**: Support skipping particular validation with `shouldValidate` callback.
- **[Breaking]**: The `resetToPure` method on both GladeInput and GladeModel has been renamed to `setAsNewPure`. This change better reflects the method's behavior of setting a new pure state rather than resetting to the original state.
- **[Add]**: New `resetToPure` method added to both GladeInput and GladeModel. This method truly resets the input(s) to their initial value(s) and marks them as pure.
Expand Down
15 changes: 15 additions & 0 deletions glade_forms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,21 @@ StringInput is specialized variant of GladeInput<String> which has additional, s

Moreover `StringInput` by default uses TextEditingController under the hood.

#### IntInput

IntInput is specialized variant of GladeInput<int> which has additional, int related, validations such as `isBetween`, `isMin`, `isMax` and more.

- `isBetween` - checks if value is between min and max value. It has optional parameter `inclusiveInterval` which defines if min and max values are included in range.
- `isMin` - checks if value is greater or equal to min value.
- `isMax` - checks if value is less or equal to max value.

Moreover `IntInput` by default uses TextEditingController under the hood.

```dart
final validator = (IntValidator()..isMax(max: 10)).build();
final result = validator.validate(5); // valid
```

### Validation

Validation is defined through part methods on ValidatorFactory such as `notNull()`, `satisfy()` and other parts.
Expand Down
12 changes: 10 additions & 2 deletions glade_forms/example/lib/example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import 'package:glade_forms/glade_forms.dart';
// ! When updating dont forget to update README.md quickstart as well
class _Model extends GladeModel {
late StringInput name;
late GladeInput<int> age;
late IntInput age;
late StringInput email;
late IntInput income;

@override
List<GladeInput<Object?>> get inputs => [name, age, email];
List<GladeInput<Object?>> get inputs => [name, age, email, income];

@override
void initialize() {
name = GladeInput.stringInput(inputKey: 'name');
age = GladeInput.intInput(value: 0, inputKey: 'age');
email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email');
income = GladeInput.intInput(
value: 10000, validator: (validator) => (validator..isMin(min: 1000)).build(), inputKey: 'income');

super.initialize();
}
Expand Down Expand Up @@ -48,6 +51,11 @@ class Example extends StatelessWidget {
validator: model.email.textFormFieldInputValidator,
decoration: const InputDecoration(labelText: 'Email'),
),
TextFormField(
controller: model.income.controller,
validator: model.income.textFormFieldInputValidator,
decoration: const InputDecoration(labelText: 'income'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: model.isValid ? () {} : null,
Expand Down
1 change: 1 addition & 0 deletions glade_forms/lib/src/core/glade_error_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ class GladeErrorKeys {
static const String stringExactLength = 'string-exact-length';
static const String valueIsNull = 'value-is-null';
static const String valueIsEmpty = 'value-is-empty';
static const String intCompareError = 'int-compare-error';
}
45 changes: 25 additions & 20 deletions glade_forms/lib/src/core/glade_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import 'package:meta/meta.dart';
typedef ValueComparator<T> = bool Function(T? initial, T? value);
typedef ValidatorFactory<T> = ValidatorInstance<T> Function(GladeValidator<T> v);
typedef StringValidatorFactory = ValidatorInstance<String> Function(StringValidator validator);
typedef IntValidatorFactory = ValidatorInstance<int> Function(IntValidator validator);
typedef OnChange<T> = void Function(ChangesInfo<T> info);
typedef OnDependencyChange = void Function(List<String> updateInputKeys);
typedef ValueTransform<T> = T Function(T input);

typedef StringInput = GladeInput<String>;
typedef IntInput = GladeInput<int>;

class GladeInput<T> {
/// Compares initial and current value.
Expand Down Expand Up @@ -331,7 +333,7 @@ class GladeInput<T> {
required int value,
String? inputKey,
int? initialValue,
ValidatorFactory<int>? validator,
IntValidatorFactory? validator,
petrnymsa marked this conversation as resolved.
Show resolved Hide resolved
bool pure = true,
ErrorTranslator<int>? translateError,
DefaultTranslations? defaultTranslations,
Expand All @@ -343,25 +345,28 @@ class GladeInput<T> {
bool useTextEditingController = false,
ValueTransform<int>? valueTransform,
bool trackUnchanged = true,
}) =>
GladeInput.create(
value: value,
initialValue: initialValue ?? value,
validator: validator,
pure: pure,
translateError: translateError,
defaultTranslations: defaultTranslations,
valueComparator: valueComparator,
inputKey: inputKey,
dependencies: dependencies,
valueConverter: GladeTypeConverters.intConverter,
onChange: onChange,
onDependencyChange: onDependencyChange,
textEditingController: textEditingController,
useTextEditingController: useTextEditingController,
valueTransform: valueTransform,
trackUnchanged: trackUnchanged,
);
}) {
final validatorInstance = validator?.call(IntValidator()) ?? IntValidator().build();

return GladeInput._(
value: value,
initialValue: initialValue ?? value,
validatorInstance: validatorInstance,
isPure: pure,
translateError: translateError,
defaultTranslations: defaultTranslations,
valueComparator: valueComparator,
inputKey: inputKey,
dependenciesFactory: dependencies,
stringTovalueConverter: GladeTypeConverters.intConverter,
onChange: onChange,
onDependencyChange: onDependencyChange,
textEditingController: textEditingController,
useTextEditingController: useTextEditingController,
valueTransform: valueTransform,
trackUnchanged: trackUnchanged,
);
}

static GladeInput<bool> boolInput({
required bool value,
Expand Down
51 changes: 51 additions & 0 deletions glade_forms/lib/src/validator/int_validator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:glade_forms/src/src.dart';

class IntValidator extends GladeValidator<int> {
IntValidator();

/// Compares given value with [min] and [max] values. With [inclusiveInterval] set to true(default), the comparison is inclusive.
void isBetween({
required int min,
required int max,
OnValidateError<int>? devError,
Object? key,
ShouldValidateCallback<int>? shouldValidate,
bool inclusiveInterval = true,
}) =>
satisfy(
(value) => inclusiveInterval ? value >= min && value <= max : value > min && value < max,
devError: devError ??
(value) =>
'Value ${value ?? 'NULL'} (inclusiveInterval: $inclusiveInterval) is not in between $min and $max.',
key: key ?? GladeErrorKeys.intCompareError,
shouldValidate: shouldValidate,
);

/// Compares given value with [min] value.
void isMin({
required int min,
OnValidateError<int>? devError,
Object? key,
ShouldValidateCallback<int>? shouldValidate,
}) =>
satisfy(
(value) => value >= min,
devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is less than $min.',
key: key ?? GladeErrorKeys.intCompareError,
shouldValidate: shouldValidate,
);

/// Compares given value with [max] value.
void isMax({
required int max,
OnValidateError<int>? devError,
Object? key,
ShouldValidateCallback<int>? shouldValidate,
}) =>
satisfy(
(value) => value <= max,
devError: devError ?? (value) => 'Value ${value ?? 'NULL'} is bigger than $max.',
key: key ?? GladeErrorKeys.intCompareError,
shouldValidate: shouldValidate,
);
}
1 change: 1 addition & 0 deletions glade_forms/lib/src/validator/validator.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export 'glade_validator.dart';
export 'int_validator.dart';
export 'part/part.dart';
export 'regex_patterns.dart';
export 'string_validator.dart';
Expand Down
102 changes: 102 additions & 0 deletions glade_forms/test/int_validator_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// ignore_for_file: avoid-unsafe-collection-methods

import 'package:glade_forms/src/validator/int_validator.dart';
import 'package:test/test.dart';

void main() {
group('inBetween', () {
test('success', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10)).build();

final result = validator.validate(6);

expect(result.isValid, isTrue);
expect(result.isInvalid, isFalse);
});

test('success inclusive max', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10)).build();

final result = validator.validate(10);

expect(result.isValid, isTrue);
expect(result.isInvalid, isFalse);
});

test('success inclusive min', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10)).build();

final result = validator.validate(5);

expect(result.isValid, isTrue);
expect(result.isInvalid, isFalse);
});

test('fails non-inclusive max', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10, inclusiveInterval: false)).build();

final result = validator.validate(10);

expect(result.isValid, isFalse);
expect(result.isInvalid, isTrue);
});

test('fails non-inclusive min', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10, inclusiveInterval: false)).build();

final result = validator.validate(5);

expect(result.isValid, isFalse);
expect(result.isInvalid, isTrue);
});

test('fails', () {
final validator = (IntValidator()..isBetween(min: 5, max: 10)).build();

final result = validator.validate(1);

expect(result.isValid, isFalse);
expect(result.isInvalid, isTrue);
});
});

group('isMax', () {
test('success', () {
final validator = (IntValidator()..isMax(max: 10)).build();

final result = validator.validate(5);

expect(result.isValid, isTrue);
expect(result.isInvalid, isFalse);
});

test('fails', () {
final validator = (IntValidator()..isMax(max: 10)).build();

final result = validator.validate(25);

expect(result.isValid, isFalse);
expect(result.isInvalid, isTrue);
});
});

group('isMin', () {
test('success', () {
final validator = (IntValidator()..isMin(min: 1)).build();

final result = validator.validate(5);

expect(result.isValid, isTrue);
expect(result.isInvalid, isFalse);
});

test('fails', () {
final validator = (IntValidator()..isMin(min: 10)).build();

final result = validator.validate(5);

expect(result.isValid, isFalse);
expect(result.isInvalid, isTrue);
});
});
}
15 changes: 13 additions & 2 deletions storybook/lib/usecases/quickstart_example.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import 'package:glade_forms_storybook/shared/usecase_container.dart';

class _Model extends GladeModel {
late StringInput name;
late GladeInput<int> age;
late IntInput age;
late StringInput email;
late IntInput income;

@override
List<GladeInput<Object?>> get inputs => [name, age, email];
List<GladeInput<Object?>> get inputs => [name, age, email, income];

_Model();

Expand All @@ -17,6 +18,11 @@ class _Model extends GladeModel {
name = GladeInput.stringInput(inputKey: 'name');
age = GladeInput.intInput(value: 0, inputKey: 'age', useTextEditingController: true);
email = GladeInput.stringInput(validator: (validator) => (validator..isEmail()).build(), inputKey: 'email');
income = GladeInput.intInput(
value: 10000,
validator: (validator) => (validator..isMin(min: 1000)).build(),
inputKey: 'income',
);

super.initialize();
}
Expand Down Expand Up @@ -53,6 +59,11 @@ class QuickStartExample extends StatelessWidget {
validator: model.email.textFormFieldInputValidator,
decoration: const InputDecoration(labelText: 'Email'),
),
TextFormField(
controller: model.income.controller,
validator: model.income.textFormFieldInputValidator,
decoration: const InputDecoration(labelText: 'Income'),
),
const SizedBox(height: 10),
ElevatedButton(
onPressed: model.isValid
Expand Down