Skip to content

Commit

Permalink
Merge pull request #1272 from flutter-form-builder-ecosystem/fix-1252…
Browse files Browse the repository at this point in the history
…-dropdown-reset

fix: add deep compare to update dropdown items
  • Loading branch information
deandreamatias committed Jul 22, 2023
2 parents d799db5 + 3487bfb commit 8d17143
Show file tree
Hide file tree
Showing 4 changed files with 323 additions and 46 deletions.
11 changes: 11 additions & 0 deletions lib/src/extensions/generic_validator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
extension GenericValidator<T> on T? {
bool emptyValidator() {
if (this == null) return true;
if (this is Iterable) return (this as Iterable).isEmpty;
if (this is String) return (this as String).isEmpty;
if (this is List) return (this as List).isEmpty;
if (this is Map) return (this as Map).isEmpty;
if (this is Set) return (this as Set).isEmpty;
return false;
}
}
25 changes: 24 additions & 1 deletion lib/src/fields/form_builder_dropdown.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_form_builder/src/extensions/generic_validator.dart';

/// Field for Dropdown button
class FormBuilderDropdown<T> extends FormBuilderFieldDecoration<T> {
Expand Down Expand Up @@ -299,7 +301,28 @@ class _FormBuilderDropdownState<T>
@override
void didUpdateWidget(covariant FormBuilderDropdown<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.items != oldWidget.items) {

final oldValues = oldWidget.items.map((e) => e.value).toList();
final currentlyValues = widget.items.map((e) => e.value).toList();
final oldChilds = oldWidget.items.map((e) => e.child.toString()).toList();
final currentlyChilds =
widget.items.map((e) => e.child.toString()).toList();

if (!currentlyValues.contains(initialValue) &&
!initialValue.emptyValidator()) {
assert(
currentlyValues.contains(initialValue) && initialValue.emptyValidator(),
'The initialValue [$initialValue] is not in the list of items or is not null or empty. '
'Please provide one of the items as the initialValue or update your initial value. '
'By default, will apply [null] to field value',
);
setValue(null);
}

if ((!listEquals(oldChilds, currentlyChilds) ||
!listEquals(oldValues, currentlyValues)) &&
(currentlyValues.contains(initialValue) ||
initialValue.emptyValidator())) {
setValue(initialValue);
}
}
Expand Down
45 changes: 0 additions & 45 deletions test/form_builder_dropdown_test.dart

This file was deleted.

288 changes: 288 additions & 0 deletions test/src/fields/form_builder_dropdown_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/src/fields/form_builder_dropdown.dart';
import 'package:flutter_test/flutter_test.dart';

import '../../form_builder_tester.dart';

void main() {
group('FormBuilderDropdown --', () {
testWidgets('basic', (WidgetTester tester) async {
const widgetName = 'd1';
final testWidget = FormBuilderDropdown<int>(
name: widgetName,
items: const [
DropdownMenuItem(
value: 1,
child: Text('One'),
),
DropdownMenuItem(
value: 2,
child: Text('Two'),
),
DropdownMenuItem(
value: 3,
child: Text('Three'),
),
],
);
final widgetFinder = find.byWidget(testWidget);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

expect(formSave(), isTrue);
expect(formValue(widgetName), isNull);
await tester.tap(widgetFinder);
await tester.pumpAndSettle();
await tester.tap(find.text('Three').last);
await tester.pumpAndSettle();
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(3));

await tester.tap(find.text('Three').last);
await tester.pumpAndSettle();
await tester.tap(find.text('One').last);
await tester.pumpAndSettle();
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));
});
testWidgets('reset to initial value when update items',
(WidgetTester tester) async {
const widgetName = 'dropdown_field';
const buttonKey = Key('update_button');

// Define the initial and updated items for the dropdown
const List<DropdownMenuItem<int>> initialItems = [
DropdownMenuItem(value: 1, child: Text('Option 1')),
DropdownMenuItem(value: 2, child: Text('Option 2')),
];

const List<DropdownMenuItem<int>> updatedItems = [
DropdownMenuItem(value: 3, child: Text('Option 3')),
DropdownMenuItem(value: 4, child: Text('Option 4')),
];

// Build the test widget tree with the initial items
const testWidget = MyTestWidget(
initialItems: initialItems,
updatedItems: updatedItems,
initialValue: 1,
updatedInitialValue: 3,
fieldName: widgetName,
buttonKey: buttonKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Verify the initial value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));

// Tap button and update the dropdown items
final buttonFinder = find.byKey(buttonKey);
await tester.tap(buttonFinder);
await tester.pumpAndSettle();

// Verify the updated value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(3));
});
testWidgets('reset to initial value when update items with same values',
(WidgetTester tester) async {
const widgetName = 'dropdown_field';
const buttonKey = Key('update_button');

// Define the initial and updated items for the dropdown
const List<DropdownMenuItem<int>> initialItems = [
DropdownMenuItem(value: 1, child: Text('Option 1')),
DropdownMenuItem(value: 2, child: Text('Option 2')),
];

const List<DropdownMenuItem<int>> updatedItems = [
DropdownMenuItem(value: 1, child: Text('Option 3')),
DropdownMenuItem(value: 2, child: Text('Option 4')),
];

// Build the test widget tree with the initial items
const testWidget = MyTestWidget(
initialItems: initialItems,
updatedItems: updatedItems,
initialValue: 1,
fieldName: widgetName,
buttonKey: buttonKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Verify the initial value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));

// Update dropdown selected value
await tester.tap(find.byType(FormBuilderDropdown<int>));
await tester.pumpAndSettle();
await tester.tap(find.text('Option 2'));
await tester.pumpAndSettle();
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(2));

// Tap button and update the dropdown items
final buttonFinder = find.byKey(buttonKey);
await tester.tap(buttonFinder);
await tester.pumpAndSettle();

// Verify the updated value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));
});

testWidgets('reset to initial value when update items with same children',
(WidgetTester tester) async {
const widgetName = 'dropdown_field';
const buttonKey = Key('update_button');
const option1 = Text('Option 1');
const option2 = Text('Option 2');

// Define the initial and updated items for the dropdown
const List<DropdownMenuItem<int>> initialItems = [
DropdownMenuItem(value: 1, child: option1),
DropdownMenuItem(value: 2, child: option2),
];

const List<DropdownMenuItem<int>> updatedItems = [
DropdownMenuItem(value: 3, child: option1),
DropdownMenuItem(value: 4, child: option2),
];

// Build the test widget tree with the initial items
const testWidget = MyTestWidget(
initialItems: initialItems,
updatedItems: updatedItems,
initialValue: 1,
updatedInitialValue: 3,
fieldName: widgetName,
buttonKey: buttonKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Verify the initial value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));

// Update dropdown selected value
await tester.tap(find.byType(FormBuilderDropdown<int>));
await tester.pumpAndSettle();
await tester.tap(find.text('Option 2'));
await tester.pumpAndSettle();
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(2));

// Tap button and update the dropdown items
final buttonFinder = find.byKey(buttonKey);
await tester.tap(buttonFinder);
await tester.pumpAndSettle();

// Verify the updated value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(3));
});
testWidgets('maintain initial value when update to equals items',
(WidgetTester tester) async {
const widgetName = 'dropdown_field';
const buttonKey = Key('update_button');

// Define the initial and updated items for the dropdown
const List<DropdownMenuItem<int>> initialItems = [
DropdownMenuItem(value: 1, child: Text('Option 1')),
DropdownMenuItem(value: 2, child: Text('Option 2')),
];

// Build the test widget tree with the initial items
const testWidget = MyTestWidget(
initialItems: initialItems,
updatedItems: initialItems,
initialValue: 1,
fieldName: widgetName,
buttonKey: buttonKey,
);
await tester.pumpWidget(buildTestableFieldWidget(testWidget));

// Verify the initial value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(1));

// Update dropdown selected value
await tester.tap(find.byType(FormBuilderDropdown<int>));
await tester.pumpAndSettle();
await tester.tap(find.text('Option 2'));
await tester.pumpAndSettle();
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(2));

// Tap button and update the dropdown items
final buttonFinder = find.byKey(buttonKey);
await tester.tap(buttonFinder);
await tester.pumpAndSettle();

// Verify the updated value of field
expect(formSave(), isTrue);
expect(formValue(widgetName), equals(2));
});
});
}

class MyTestWidget<T> extends StatefulWidget {
final List<DropdownMenuItem<T>> initialItems;
final List<DropdownMenuItem<T>> updatedItems;
final T? initialValue;
final T? updatedInitialValue;
final String fieldName;
final Key? buttonKey;

const MyTestWidget({
super.key,
required this.initialItems,
this.initialValue,
this.updatedItems = const [],
required this.fieldName,
required this.buttonKey,
this.updatedInitialValue,
});

@override
State<MyTestWidget> createState() => _MyTestWidgetState<T>();
}

class _MyTestWidgetState<T> extends State<MyTestWidget> {
T? _initialValue;
List<DropdownMenuItem<T>> _items = [];

@override
void initState() {
super.initState();
_items = widget.initialItems as List<DropdownMenuItem<T>>;
_initialValue = widget.initialValue;
}

@override
Widget build(BuildContext context) {
return Column(
children: [
FormBuilderDropdown<T>(
name: 'dropdown_field',
items: _items,
initialValue: _initialValue,
onChanged: (value) {},
),
ElevatedButton(
key: widget.buttonKey,
onPressed: () {
setState(() {
_items = widget.updatedItems as List<DropdownMenuItem<T>>;
if (widget.updatedInitialValue != null) {
_initialValue = widget.updatedInitialValue;
}
});
},
child: const Text('update'),
)
],
);
}
}

0 comments on commit 8d17143

Please sign in to comment.