From 1da137db1fbf65f0a09ef1c9042fb5e658dbeda3 Mon Sep 17 00:00:00 2001 From: Manish Kumar <manish.kumar@etu.unige.ch> Date: Tue, 9 Feb 2021 19:22:32 +0100 Subject: [PATCH 1/3] improved dynamicForm and Popup tests + new test for createEvent --- test/createEvent_test.dart | 47 +++++++++++++++++++++++ test/dynamicForm_test.dart | 76 +++++++++++++++++++++++++++----------- test/popup_test.dart | 7 +++- 3 files changed, 108 insertions(+), 22 deletions(-) create mode 100644 test/createEvent_test.dart diff --git a/test/createEvent_test.dart b/test/createEvent_test.dart new file mode 100644 index 0000000..897f65d --- /dev/null +++ b/test/createEvent_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:hive/hive.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:naca/createEvent.dart'; +import 'package:naca/dynamicForm.dart'; +import 'package:naca/models/Event.dart'; + +void main() { + Box<Event> eventDB; + + setUpAll(() async { + // We init the Hive + await Hive.initFlutter(); + // Since our classes are not native elements, we generate Adapters + Hive.registerAdapter<Event>(EventAdapter()); + // We open the boxes, we can open them anywhere. + // It is better to close them after using it. + + eventDB = await Hive.openBox<Event>('event'); + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + testWidgets('Create event screen is displayed and button works', + (WidgetTester tester) async { + await tester.pumpWidget(makeTesteableWidget(child: CreateEvent())); + + // Test des éléments de départ + expect(find.text("Créer un événement"), findsOneWidget); + expect(find.byType(FloatingActionButton), findsOneWidget); + + // Test d'apparition du popup + await tester.tap(find.byType(FloatingActionButton)); + await tester.pumpAndSettle(); + + expect(find.byType(DynamicForm), findsOneWidget); + }); +} + +Widget makeTesteableWidget({Widget child}) { + return MaterialApp( + home: Scaffold( + body: child, + ), + ); +} diff --git a/test/dynamicForm_test.dart b/test/dynamicForm_test.dart index f2a2f3d..e899101 100644 --- a/test/dynamicForm_test.dart +++ b/test/dynamicForm_test.dart @@ -2,28 +2,50 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:naca/dynamicForm.dart'; +import 'package:naca/primaryButton.dart'; void main() { - testWidgets('DynamicForm creates the TextFormFields', + var res; + final mandatoryForm = makeTesteableWidget( + child: Builder( + builder: (BuildContext context) => DynamicForm( + fields: [ + ['Nom', String, true], + ['Lieu', String, true], + ['Contexte', String, false], + ['Date de début', DateTime, true, DateTime.now(), false], + ['Date de fin', DateTime, false, null, false], + ], + buttonText: 'Créer', + onSubmit: (formRes) => res = formRes, + context: context, + ), + )); + + final form = makeTesteableWidget( + child: Builder( + builder: (BuildContext context) => DynamicForm( + fields: [ + ['Nom', String, false], + ['Lieu', String, false], + ['Contexte', String, false], + ['Date de début', DateTime, false, DateTime.now(), false], + ['Date de fin', DateTime, false, null, false], + ], + buttonText: 'Créer', + onSubmit: (formRes) { + print('bite'); + res = formRes; + }, + context: context, + ), + )); + + testWidgets('DynamicForm creates the TextFormFields with mandatory fields', (WidgetTester tester) async { // Create the widget by telling the tester to build it. - final form = makeTesteableWidget( - child: Builder( - builder: (BuildContext context) => DynamicForm( - fields: [ - ['Nom', String, true], - ['Lieu', String, true], - ['Contexte', String, false], - ['Date de début', DateTime, true, DateTime.now(), false], - ['Date de fin', DateTime, false, null, false], - ], - buttonText: 'Créer', - onSubmit: _res, - context: context, - ), - )); - - await tester.pumpWidget(form); + + await tester.pumpWidget(mandatoryForm); // Test du nombre de champs générés // 3 champs String et 2 champs DateTime comprenant chacun 2 champs (pour la date et pour l'heure) @@ -36,11 +58,23 @@ void main() { expect(find.text('Contexte'), findsOneWidget); expect(find.text('Date de début*'), findsOneWidget); expect(find.text('Date de fin'), findsOneWidget); + + expect(find.text('Créer'), findsOneWidget); + + await tester.tap(find.byType(PrimaryButton)); + await tester.pumpAndSettle(); + + // On s'attend à ne rien recevoir en retour, comme les champs obligatoires sont vides + expect(res, isNull); + + // TODO trouver comment tester l'activation des messages d'erreur lorsque l'on valide le formulaire vide + /*expect( + ((tester.firstWidget(find.text('*Champs obligatoires')) as Text).style) + .color, + Colors.red);*/ }); -} -_res() { - print('dummy'); + //TODO appuyer sur le bouton de validation ne semble pas marcher } Widget makeTesteableWidget({Widget child}) { diff --git a/test/popup_test.dart b/test/popup_test.dart index 21b1bee..be982c9 100644 --- a/test/popup_test.dart +++ b/test/popup_test.dart @@ -10,10 +10,15 @@ void main() { // Clique sur le bouton pour faire apparaître le popup await tester.tap(find.byType(FloatingActionButton)); - await tester.pump(); expect(find.text('test'), findsOneWidget); + + // Clique sur le bouton pour faire disparaître le popup + await tester.tap(find.byType(FloatingActionButton)); + await tester.pump(); + + expect(find.text('test'), findsNothing); }); } -- GitLab From 121824ba32f032ea3c625331092ff11d935acdf3 Mon Sep 17 00:00:00 2001 From: Manish Kumar <manish.kumar@etu.unige.ch> Date: Tue, 9 Feb 2021 19:34:25 +0100 Subject: [PATCH 2/3] small fixes --- test/createEvent_test.dart | 4 +--- test/dynamicForm_test.dart | 19 ------------------- test/popup_test.dart | 2 +- 3 files changed, 2 insertions(+), 23 deletions(-) diff --git a/test/createEvent_test.dart b/test/createEvent_test.dart index 897f65d..64aa95e 100644 --- a/test/createEvent_test.dart +++ b/test/createEvent_test.dart @@ -8,8 +8,6 @@ import 'package:naca/dynamicForm.dart'; import 'package:naca/models/Event.dart'; void main() { - Box<Event> eventDB; - setUpAll(() async { // We init the Hive await Hive.initFlutter(); @@ -18,7 +16,7 @@ void main() { // We open the boxes, we can open them anywhere. // It is better to close them after using it. - eventDB = await Hive.openBox<Event>('event'); + await Hive.openBox<Event>('event'); TestWidgetsFlutterBinding.ensureInitialized(); }); diff --git a/test/dynamicForm_test.dart b/test/dynamicForm_test.dart index e899101..0fec8f0 100644 --- a/test/dynamicForm_test.dart +++ b/test/dynamicForm_test.dart @@ -22,25 +22,6 @@ void main() { ), )); - final form = makeTesteableWidget( - child: Builder( - builder: (BuildContext context) => DynamicForm( - fields: [ - ['Nom', String, false], - ['Lieu', String, false], - ['Contexte', String, false], - ['Date de début', DateTime, false, DateTime.now(), false], - ['Date de fin', DateTime, false, null, false], - ], - buttonText: 'Créer', - onSubmit: (formRes) { - print('bite'); - res = formRes; - }, - context: context, - ), - )); - testWidgets('DynamicForm creates the TextFormFields with mandatory fields', (WidgetTester tester) async { // Create the widget by telling the tester to build it. diff --git a/test/popup_test.dart b/test/popup_test.dart index be982c9..9ce25dd 100644 --- a/test/popup_test.dart +++ b/test/popup_test.dart @@ -4,7 +4,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:naca/popup.dart'; void main() { - testWidgets('The Popup appears and displays the correct widget', + testWidgets('The Popup appears, displays the correct widget and disappears', (WidgetTester tester) async { await tester.pumpWidget(makeTesteableWidget(child: PopupTestWidget())); -- GitLab From 723ea146718ec1069d670051853594735a71dcd3 Mon Sep 17 00:00:00 2001 From: Manish Kumar <manish.kumar@etu.unige.ch> Date: Wed, 10 Feb 2021 13:24:28 +0100 Subject: [PATCH 3/3] updated some comments and cleaned up the code --- lib/dynamicForm.dart | 116 ++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/lib/dynamicForm.dart b/lib/dynamicForm.dart index 0a23936..30cc967 100644 --- a/lib/dynamicForm.dart +++ b/lib/dynamicForm.dart @@ -92,7 +92,7 @@ class DynamicFormState extends State<DynamicForm> { TextEditingController dateController; TextEditingController timeController; - // La boucle Future.forEach assure que tout soit exécuté avant de passer au prochain tour de boucle + // Itération sur les champs widget.fields.forEach( (field) { if (field[1] == DateTime) { @@ -102,7 +102,8 @@ class DynamicFormState extends State<DynamicForm> { // S'il y a une valeur par défaut et qu'on souhaite qu'elle soit affichée if (field.length > 4 && field[4]) { dateController.text = dateFormater(field[3]); - // On est obligé d'attendre que la variable context soit accessible + // On utilise le contexte du parent qui facilite l'appel, + // sinon il faudrait faire un appel asynchrone qui ne smblait pas fonctionner dans les tests timeController.text = TimeOfDay.fromDateTime(field[3]).format(widget.context); } @@ -143,8 +144,7 @@ class DynamicFormState extends State<DynamicForm> { // Variable possédant tous les éléments du formulaire à afficher final processedFields = <Widget>[]; var placeholder; - final node = FocusScope.of(context); - var editingMode; + var textInputAction; // Construction et ajout des champs selon les paramètres entrés for (int i = 0; i < controllers.entries.length; i++) { @@ -159,19 +159,19 @@ class DynamicFormState extends State<DynamicForm> { // Champ de type String if (field.value['type'] == String) { - // Assignation de editingMode qui détermine si l'utilisateur peut accéder au champ suivant - // directement depuis le clavier. C'est désactivé (unfocus) si c'est le dernier champ ou si + // Assignation de textInputAction qui détermine si l'utilisateur peut accéder au champ suivant + // directement depuis le clavier. C'est désactivé (done) si c'est le dernier champ ou si // le prochain champ est de type DateTime et requiert que l'utilisateur tape sur le champ pour faire // apparaître le calendrier ou l'horloge if (i == controllers.entries.length - 1 || controllers.entries.elementAt(i + 1).value['type'] == DateTime) - editingMode = () => node.unfocus(); + textInputAction = TextInputAction.done; else - editingMode = () => node.nextFocus(); + textInputAction = TextInputAction.next; processedFields.add( _buildStringField(placeholder, field.value['required'], context, - controllers[field.key]['fieldController'], editingMode), + controllers[field.key]['fieldController'], textInputAction), ); // Champ de type DateTime } else if (field.value['type'] == DateTime) { @@ -249,51 +249,7 @@ class DynamicFormState extends State<DynamicForm> { body: widget.buttonText, fontSize: 30.0, padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30), - onPressed: () { - // Test de validité du formulaire - if (_formKey.currentState.validate()) { - // Dictionnaire en retour avec les bons types - var resMap = Map(); - controllers.entries.forEach( - (controller) { - if (controller.value['type'] == String) { - controller.value.containsKey('value') && - controller.value['fieldController'].text.length == 0 - ? resMap[controller.key] = controller.value['value'] - : resMap[controller.key] = - controller.value['fieldController'].text; - } else if (controller.value['type'] == DateTime) { - // Combinaison des champs date et heure si la date n'est pas null - var selectedDate = controller.value['dateValue']; - var selectedTime = controller.value['timeValue']; - // Test de tout type d'entrée possible, qui peuvent arriver si le champ est optionnel - if (selectedDate == null) { - resMap[controller.key] = null; - } else if (selectedDate != null && selectedTime == null) { - resMap[controller.key] = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - ); - } else { - resMap[controller.key] = DateTime( - selectedDate.year, - selectedDate.month, - selectedDate.day, - selectedTime.hour, - selectedTime.minute, - ); - } - } - }, - ); - widget.onSubmit(resMap); - } else { - setState(() { - validForm = false; - }); - } - }, + onPressed: validateForm, ), ), ); @@ -301,6 +257,54 @@ class DynamicFormState extends State<DynamicForm> { return processedFields; } + /// Méthode validant le formulaire + void validateForm() { + // Test de validité du formulaire + if (_formKey.currentState.validate()) { + // Dictionnaire en retour avec les bons types + var resMap = Map(); + controllers.entries.forEach( + (controller) { + if (controller.value['type'] == String) { + controller.value.containsKey('value') && + controller.value['fieldController'].text.length == 0 + ? resMap[controller.key] = controller.value['value'] + : resMap[controller.key] = + controller.value['fieldController'].text; + } else if (controller.value['type'] == DateTime) { + // Combinaison des champs date et heure si la date n'est pas null + var selectedDate = controller.value['dateValue']; + var selectedTime = controller.value['timeValue']; + // Test de tout type d'entrée possible, qui peuvent arriver si le champ est optionnel + if (selectedDate == null) { + resMap[controller.key] = null; + } else if (selectedDate != null && selectedTime == null) { + resMap[controller.key] = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + ); + } else { + resMap[controller.key] = DateTime( + selectedDate.year, + selectedDate.month, + selectedDate.day, + selectedTime.hour, + selectedTime.minute, + ); + } + } + }, + ); + // Appel de la fonction passée en paramètre au widget + widget.onSubmit(resMap); + } else { + setState(() { + validForm = false; + }); + } + } + @override Widget build(BuildContext context) { // Construction du formulaire avec la clé créée @@ -353,7 +357,7 @@ Widget _buildStringField( bool requiredField, BuildContext context, TextEditingController controller, - Function onEditingComplete, + TextInputAction textInputAction, ) { return Container( margin: EdgeInsets.only(top: 20), @@ -362,7 +366,7 @@ Widget _buildStringField( validator: customValidator(requiredField), decoration: _buildInputDecoration(name, context), controller: controller, - onEditingComplete: onEditingComplete, + textInputAction: textInputAction, style: TextStyle(color: Theme.of(context).primaryColor), ), ); -- GitLab