Skip to content

Commit

Permalink
Merge pull request #1232 from flutter-form-builder-ecosystem/improve-…
Browse files Browse the repository at this point in the history
…autovalidate-modes

Improve autovalidate modes
  • Loading branch information
deandreamatias authored Apr 30, 2023
2 parents f5a831b + 0e0f0e8 commit 76a1074
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 38 deletions.
6 changes: 2 additions & 4 deletions example/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}

dependencies {
Expand All @@ -14,15 +14,13 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}

Expand Down
8 changes: 8 additions & 0 deletions lib/src/extensions/autovalidatemode_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:flutter/material.dart';

extension AutovalidateModeExtension on AutovalidateMode {
/// Is always or is onUserInteraction
bool get isEnable => isAlways || isOnUserInteraction;
bool get isAlways => this == AutovalidateMode.always;
bool get isOnUserInteraction => this == AutovalidateMode.onUserInteraction;
}
16 changes: 5 additions & 11 deletions lib/src/form_builder.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/src/extensions/autovalidatemode_extension.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';

/// A container for form fields.
Expand Down Expand Up @@ -151,23 +152,16 @@ class FormBuilderState extends State<FormBuilder> {
initialValue[name];
}

void setInternalFieldValue<T>(String name, T? value,
{required bool isSetState}) {
void setInternalFieldValue<T>(String name, T? value) {
_instantValue[name] = value;
if (isSetState) {
setState(() {});
if (widget.autovalidateMode?.isEnable ?? false) {
validate();
}
widget.onChanged?.call();
}

void removeInternalFieldValue(
String name, {
required bool isSetState,
}) {
void removeInternalFieldValue(String name) {
_instantValue.remove(name);
if (isSetState) {
setState(() {});
}
}

void registerField(String name, FormBuilderFieldState field) {
Expand Down
28 changes: 13 additions & 15 deletions lib/src/form_builder_field.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_form_builder/src/extensions/autovalidatemode_extension.dart';

enum OptionsOrientation { horizontal, vertical, wrap }

Expand Down Expand Up @@ -51,7 +52,7 @@ class FormBuilderField<T> extends FormField<T> {
super.key,
super.onSaved,
super.initialValue,
super.autovalidateMode = AutovalidateMode.onUserInteraction,
super.autovalidateMode,
super.enabled = true,
super.validator,
super.restorationId,
Expand Down Expand Up @@ -106,6 +107,12 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>

bool get enabled => widget.enabled && (_formBuilderState?.enabled ?? true);
bool get _readOnly => !(_formBuilderState?.widget.skipDisabled ?? false);
bool get _isEnableValidate =>
widget.autovalidateMode.isEnable ||
(_formBuilderState?.widget.autovalidateMode?.isEnable ?? false);
bool get _isAlwaysValidate =>
widget.autovalidateMode.isAlways ||
(_formBuilderState?.widget.autovalidateMode?.isAlways ?? false);

/// Will be true if the field is dirty
///
Expand Down Expand Up @@ -145,10 +152,7 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
focusAttachment = effectiveFocusNode.attach(context);

// Verify if need auto validate form
if ((enabled || _readOnly) &&
(widget.autovalidateMode == AutovalidateMode.always ||
_formBuilderState?.widget.autovalidateMode ==
AutovalidateMode.always)) {
if ((enabled || _readOnly) && _isAlwaysValidate) {
WidgetsBinding.instance.addPostFrameCallback((_) {
validate();
});
Expand Down Expand Up @@ -186,17 +190,11 @@ class FormBuilderFieldState<F extends FormBuilderField<T>, T>
if (_formBuilderState != null) {
_dirty = true;
if (enabled || _readOnly) {
_formBuilderState!.setInternalFieldValue<T>(
widget.name,
value,
isSetState: false,
);
} else {
_formBuilderState!.removeInternalFieldValue(
widget.name,
isSetState: false,
);
_formBuilderState!.setInternalFieldValue<T>(widget.name, value);
if (_isEnableValidate) validate();
return;
}
_formBuilderState!.removeInternalFieldValue(widget.name);
}
}

Expand Down
127 changes: 121 additions & 6 deletions test/src/form_builder_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,106 @@ void main() {
});
});

group('isValid -', () {
testWidgets('Should invalid when set custom error', (tester) async {
final textFieldKey = GlobalKey<FormBuilderFieldState>();
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
key: textFieldKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Set custom error
textFieldKey.currentState?.invalidate(errorTextField);
await tester.pumpAndSettle();

expect(textFieldKey.currentState?.isValid, isFalse);
});
testWidgets(
'Should valid when no has error and autovalidateMode is always',
(tester) async {
final textFieldKey = GlobalKey<FormBuilderFieldState>();
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
key: textFieldKey,
autovalidateMode: AutovalidateMode.always,
validator: (value) =>
value == null || value.isEmpty ? errorTextField : null,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

expect(textFieldKey.currentState?.isValid, isFalse);

final widgetFinder = find.byWidget(testWidget);
await tester.enterText(widgetFinder, 'test');
await tester.pumpAndSettle();

expect(textFieldKey.currentState?.isValid, isTrue);
});
testWidgets(
'Should invalid when has error and autovalidateMode is always',
(tester) async {
final textFieldKey = GlobalKey<FormBuilderFieldState>();
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
key: textFieldKey,
autovalidateMode: AutovalidateMode.always,
validator: (value) =>
value == null || value.length < 10 ? errorTextField : null,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

expect(textFieldKey.currentState?.isValid, isFalse);

final widgetFinder = find.byWidget(testWidget);
await tester.enterText(widgetFinder, 'test');
await tester.pumpAndSettle();

expect(textFieldKey.currentState?.isValid, isFalse);
});
});

group('hasErrors -', () {
testWidgets('Should has errors when set custom error', (tester) async {
final textFieldKey = GlobalKey<FormBuilderFieldState>();
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
key: textFieldKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Set custom error
textFieldKey.currentState?.invalidate(errorTextField);
await tester.pumpAndSettle();

expect(textFieldKey.currentState?.hasError, isTrue);
});
testWidgets('Should no has errors when is empty and no has validators',
(tester) async {
final textFieldKey = GlobalKey<FormBuilderFieldState>();
const textFieldName = 'text';
final testWidget = FormBuilderTextField(
name: textFieldName,
key: textFieldKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Set custom error
textFieldKey.currentState?.validate();
await tester.pumpAndSettle();

expect(textFieldKey.currentState?.hasError, isFalse);
});
});

group('autovalidateMode -', () {
testWidgets(
'Should show error when init form and AutovalidateMode is always',
Expand All @@ -33,14 +133,29 @@ void main() {
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
validator: (value) => errorTextField,
validator: (value) =>
value == null || value.isEmpty ? errorTextField : null,
autovalidateMode: AutovalidateMode.always,
);
await tester.pumpWidget(
buildTestableFieldWidget(
testWidget,
autovalidateMode: AutovalidateMode.always,
),
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
await tester.pumpAndSettle();

expect(find.text(errorTextField), findsOneWidget);
});
testWidgets(
'Should show error when AutovalidateMode is onUserInteraction and change field',
(tester) async {
const textFieldName = 'text4';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (value) => errorTextField,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
expect(find.text(errorTextField), findsNothing);

await tester.enterText(find.byWidget(testWidget), 'hola');
await tester.pumpAndSettle();

expect(find.text(errorTextField), findsOneWidget);
Expand Down
65 changes: 63 additions & 2 deletions test/src/form_builder_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,65 @@ void main() {
);
});

group('isValid -', () {
testWidgets('Should invalid when set custom error', (tester) async {
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(name: textFieldName);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Set custom error
formKey.currentState?.fields[textFieldName]?.invalidate(errorTextField);
await tester.pumpAndSettle();

expect(formKey.currentState?.isValid, isFalse);
});
testWidgets('Should valid when no has error and autovalidateMode is always',
(tester) async {
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
validator: (value) =>
value == null || value.isEmpty ? errorTextField : null,
);
await tester.pumpWidget(buildTestableFieldWidget(
testWidget,
autovalidateMode: AutovalidateMode.always,
));

expect(formKey.currentState?.isValid, isFalse);

final widgetFinder = find.byWidget(testWidget);
await tester.enterText(widgetFinder, 'test');
await tester.pumpAndSettle();

expect(formKey.currentState?.isValid, isTrue);
});
testWidgets('Should invalid when has error and autovalidateMode is always',
(tester) async {
const textFieldName = 'text';
const errorTextField = 'error text field';
final testWidget = FormBuilderTextField(
name: textFieldName,
validator: (value) =>
value == null || value.length < 10 ? errorTextField : null,
);
await tester.pumpWidget(buildTestableFieldWidget(
testWidget,
autovalidateMode: AutovalidateMode.always,
));

expect(formKey.currentState?.isValid, isFalse);

final widgetFinder = find.byWidget(testWidget);
await tester.enterText(widgetFinder, 'test');
await tester.pumpAndSettle();

expect(formKey.currentState?.isValid, isFalse);
});
});

group('skipDisabled -', () {
testWidgets(
'Should not show error when field is not enabled and skipDisabled is true',
Expand Down Expand Up @@ -177,9 +236,11 @@ void main() {
final testWidget = FormBuilderTextField(
name: textFieldName,
validator: (value) => errorTextField,
autovalidateMode: AutovalidateMode.always,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));
await tester.pumpWidget(buildTestableFieldWidget(
testWidget,
autovalidateMode: AutovalidateMode.always,
));
await tester.pumpAndSettle();

expect(find.text(errorTextField), findsOneWidget);
Expand Down

0 comments on commit 76a1074

Please sign in to comment.