Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions lib/formz.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ class Formz {
static bool isPure(List<FormzInput<dynamic, dynamic>> inputs) {
return inputs.every((input) => input.isPure);
}

/// Returns a [Set] of invalid [FormzInput] given a list of [FormzInput]
/// to be validated
static Set<FormzInput<dynamic, dynamic>> validateGranularly(
List<FormzInput<dynamic, dynamic>> inputs,
) {
return inputs.where((input) => input.isNotValid).toSet();
}
}

/// Mixin that automatically handles validation of all [FormzInput]s present in
Expand Down Expand Up @@ -192,6 +200,10 @@ mixin FormzMixin {
/// Whether at least one of the [FormzInput]s is dirty.
bool get isDirty => !isPure;

/// Returns a [Set] of invalid [FormzInput] by validating the [inputs]
Set<FormzInput<dynamic, dynamic>> get invalidInputs =>
Formz.validateGranularly(inputs);

/// Returns all [FormzInput] instances.
///
/// Override this and give it all [FormzInput]s in your class that should be
Expand Down
107 changes: 107 additions & 0 deletions test/formz_test.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Not needed for test files
// ignore_for_file: prefer_const_constructors

import 'package:formz/formz.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -39,6 +40,53 @@ void main() {
expect(form.isDirty, isTrue);
expect(form.isPure, isFalse);
});

test('for a form with 2 invalid inputs, returns 2 invalid inputs', () {
final invalidName = NameInput.dirty();
final invalidPassword = PasswordInput.dirty();
final form = NamePasswordInputFormzMixin(
name: invalidName,
password: invalidPassword,
);
final invalidInputs = form.invalidInputs;

expect(invalidInputs.length, 2);
expect(invalidInputs.contains(invalidName), isTrue);
expect(invalidInputs.contains(invalidPassword), isTrue);
});

test(
'for a form with 1 invalid and 1 valid input, returns 1 invalid input',
() {
final validName = NameInput.dirty(value: 'Name');
final invalidPassword = PasswordInput.dirty();
final form = NamePasswordInputFormzMixin(
name: validName,
password: invalidPassword,
);
final invalidInputs = form.invalidInputs;

expect(invalidInputs.length, 1);
expect(invalidInputs.contains(invalidPassword), isTrue);
},
);

test(
'form with only valid inputs, returns empty set as invalid inputs',
() {
final validName = NameInput.dirty(value: 'Name');
final validPassword = PasswordInput.dirty(
value: 'VeryGoodPassword42',
);
final form = NamePasswordInputFormzMixin(
name: validName,
password: validPassword,
);
final invalidInputs = form.invalidInputs;

expect(invalidInputs.length, 0);
},
);
});

group('FormzInputErrorCacheMixin', () {
Expand Down Expand Up @@ -246,6 +294,65 @@ void main() {
});
});

group('validateGranularly', () {
test('returns empty set for empty inputs', () {
expect(Formz.validateGranularly([]), <FormzInput<dynamic, dynamic>>{});
});

test('returns empty set for valid pure input', () {
expect(
Formz.validateGranularly([NameInput.pure(value: 'joe')]),
<FormzInput<dynamic, dynamic>>{},
);
});

test('returns empty set for valid dirty input', () {
expect(
Formz.validateGranularly([NameInput.dirty(value: 'joe')]),
<FormzInput<dynamic, dynamic>>{},
);
});

test('returns empty set for multiple valid pure/dirty input', () {
expect(
Formz.validateGranularly([
NameInput.dirty(value: 'jen'),
NameInput.pure(value: 'bob'),
NameInput.dirty(value: 'alex'),
]),
<FormzInput<dynamic, dynamic>>{},
);
});

test(
'returns one invalid input when dirty invalid input is provided',
() {
final invalidName = NameInput.dirty();
expect(Formz.validateGranularly([invalidName]), {invalidName});
},
);

test('returns one invalid input when pure invalid input is provided', () {
final invalidName = NameInput.pure();
expect(Formz.validateGranularly([invalidName]), {invalidName});
});

test('returns only invalid inputs for multiple valid/invalid inputs', () {
final invalidNameOne = NameInput.dirty();
final invalidNameTwo = NameInput.pure();
final validName = NameInput.dirty(value: 'Joe');
final result = Formz.validateGranularly([
invalidNameOne,
validName,
invalidNameTwo,
]);
expect(result.length, 2);
expect(result.contains(invalidNameOne), isTrue);
expect(result.contains(invalidNameTwo), isTrue);
expect(result.contains(validName), isFalse);
});
});

group('FormzSubmissionStatusX', () {
test('isInitial returns true', () {
expect(FormzSubmissionStatus.initial.isInitial, isTrue);
Expand Down
35 changes: 35 additions & 0 deletions test/helpers/name_input.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,28 @@ class NameInput extends FormzInput<String, NameInputError> {
}
}

enum PasswordValidationError { invalid, empty }

class PasswordInput extends FormzInput<String, PasswordValidationError> {
const PasswordInput.pure({String value = ''}) : super.pure(value);
const PasswordInput.dirty({String value = ''}) : super.dirty(value);

static final _passwordRegex = RegExp(
r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$',
);

@override
PasswordValidationError? validator(String value) {
if (value.isEmpty) {
return PasswordValidationError.empty;
} else if (!_passwordRegex.hasMatch(value)) {
return PasswordValidationError.invalid;
}

return null;
}
}

class NameInputFormzMixin with FormzMixin {
NameInputFormzMixin({this.name = const NameInput.pure()});

Expand All @@ -21,6 +43,19 @@ class NameInputFormzMixin with FormzMixin {
List<FormzInput<dynamic, dynamic>> get inputs => [name];
}

class NamePasswordInputFormzMixin with FormzMixin {
NamePasswordInputFormzMixin({
this.name = const NameInput.pure(),
this.password = const PasswordInput.pure(),
});

final NameInput name;
final PasswordInput password;

@override
List<FormzInput<dynamic, dynamic>> get inputs => [name, password];
}

// Test fixture so allowable
// ignore: must_be_immutable
class NameInputErrorCacheMixin extends FormzInput<String, NameInputError>
Expand Down