mirror of
https://github.com/wger-project/flutter.git
synced 2026-02-18 00:17:48 +01:00
Refactor WgerHttpException
We can now use a widget to show any errors returned by WgerHttpException. This has to be added on a form-by-form basis, otherwise, the general error handling shows the results in a modal dialog
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class WgerHttpException implements Exception {
|
||||
Map<String, dynamic>? errors;
|
||||
Map<String, dynamic> errors = {};
|
||||
|
||||
/// Custom http exception.
|
||||
/// Expects the response body of the REST call and will try to parse it to
|
||||
@@ -37,8 +37,12 @@ class WgerHttpException implements Exception {
|
||||
}
|
||||
}
|
||||
|
||||
WgerHttpException.fromMap(Map<String, dynamic> map) {
|
||||
errors = map;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return errors!.values.toList().join(', ');
|
||||
return errors.values.toList().join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,7 @@ void showHttpExceptionErrorDialog(WgerHttpException exception, {BuildContext? co
|
||||
return;
|
||||
}
|
||||
|
||||
logger.fine(exception.toString());
|
||||
|
||||
final errorList = extractErrors(exception.errors);
|
||||
final errorList = formatErrors(extractErrors(exception.errors));
|
||||
|
||||
showDialog(
|
||||
context: dialogContext,
|
||||
@@ -272,35 +270,78 @@ void showDeleteDialog(BuildContext context, String confirmDeleteName, Log log) a
|
||||
return res;
|
||||
}
|
||||
|
||||
List<Widget> extractErrors(Map<String, dynamic>? errors) {
|
||||
final List<Widget> errorList = [];
|
||||
class ApiError {
|
||||
final String key;
|
||||
late List<String> errorMessages = [];
|
||||
|
||||
if (errors == null) {
|
||||
return errorList;
|
||||
ApiError({required this.key, this.errorMessages = const []});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ApiError(key: $key, errorMessage: $errorMessages)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts error messages from the server response
|
||||
List<ApiError> extractErrors(Map<String, dynamic> errors) {
|
||||
final List<ApiError> errorList = [];
|
||||
|
||||
for (final key in errors.keys) {
|
||||
// Error headers
|
||||
// Ensure that the error heading first letter is capitalized.
|
||||
final String errorHeaderMsg = key[0].toUpperCase() + key.substring(1, key.length);
|
||||
// Header
|
||||
var header = key[0].toUpperCase() + key.substring(1, key.length);
|
||||
header = header.replaceAll('_', ' ');
|
||||
final error = ApiError(key: header);
|
||||
|
||||
final messages = errors[key];
|
||||
|
||||
// Messages
|
||||
if (messages is String) {
|
||||
error.errorMessages = List.of(error.errorMessages)..add(messages);
|
||||
} else {
|
||||
error.errorMessages = [...error.errorMessages, ...messages];
|
||||
}
|
||||
|
||||
errorList.add(error);
|
||||
}
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
/// Processes the error messages from the server and returns a list of widgets
|
||||
List<Widget> formatErrors(List<ApiError> errors, {Color? color}) {
|
||||
final textColor = color ?? Colors.black;
|
||||
|
||||
final List<Widget> errorList = [];
|
||||
|
||||
for (final error in errors) {
|
||||
errorList.add(
|
||||
Text(
|
||||
errorHeaderMsg.replaceAll('_', ' '),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(error.key, style: TextStyle(fontWeight: FontWeight.bold, color: textColor)),
|
||||
);
|
||||
|
||||
// Error messages
|
||||
if (errors[key] is String) {
|
||||
errorList.add(Text(errors[key]));
|
||||
} else {
|
||||
for (final value in errors[key]) {
|
||||
errorList.add(Text(value));
|
||||
}
|
||||
for (final message in error.errorMessages) {
|
||||
errorList.add(Text(message, style: TextStyle(color: textColor)));
|
||||
}
|
||||
errorList.add(const SizedBox(height: 8));
|
||||
}
|
||||
|
||||
return errorList;
|
||||
}
|
||||
|
||||
class FormErrorsWidget extends StatelessWidget {
|
||||
final WgerHttpException exception;
|
||||
|
||||
const FormErrorsWidget(this.exception, {super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Icon(Icons.error_outline, color: Theme.of(context).colorScheme.error),
|
||||
...formatErrors(
|
||||
extractErrors(exception.errors),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@ class AuthCard extends StatefulWidget {
|
||||
class _AuthCardState extends State<AuthCard> {
|
||||
bool isObscure = true;
|
||||
bool confirmIsObscure = true;
|
||||
Widget errorMessage = const SizedBox.shrink();
|
||||
|
||||
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||
AuthMode _authMode = AuthMode.Login;
|
||||
@@ -195,21 +196,25 @@ class _AuthCardState extends State<AuthCard> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
} on WgerHttpException catch (error) {
|
||||
if (mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
if (context.mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} on WgerHttpException catch (error) {
|
||||
if (context.mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
errorMessage = FormErrorsWidget(error);
|
||||
});
|
||||
}
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
rethrow;
|
||||
} catch (error) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
if (context.mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -248,6 +253,7 @@ class _AuthCardState extends State<AuthCard> {
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
children: [
|
||||
errorMessage,
|
||||
TextFormField(
|
||||
key: const Key('inputUsername'),
|
||||
decoration: InputDecoration(
|
||||
|
||||
@@ -21,8 +21,6 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/providers/auth.dart';
|
||||
import 'package:wger/providers/body_weight.dart';
|
||||
@@ -90,20 +88,13 @@ class _HomeTabsScreenState extends State<HomeTabsScreen> with SingleTickerProvid
|
||||
|
||||
// Base data
|
||||
widget._logger.info('Loading base data');
|
||||
try {
|
||||
await Future.wait([
|
||||
authProvider.setServerVersion(),
|
||||
userProvider.fetchAndSetProfile(),
|
||||
routinesProvider.fetchAndSetUnits(),
|
||||
nutritionPlansProvider.fetchIngredientsFromCache(),
|
||||
exercisesProvider.fetchAndSetInitialData(),
|
||||
]);
|
||||
} on WgerHttpException catch (error) {
|
||||
widget._logger.warning('Wger exception loading base data');
|
||||
if (mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
}
|
||||
await Future.wait([
|
||||
authProvider.setServerVersion(),
|
||||
userProvider.fetchAndSetProfile(),
|
||||
routinesProvider.fetchAndSetUnits(),
|
||||
nutritionPlansProvider.fetchIngredientsFromCache(),
|
||||
exercisesProvider.fetchAndSetInitialData(),
|
||||
]);
|
||||
|
||||
// Plans, weight and gallery
|
||||
widget._logger.info('Loading routines, weight, measurements and gallery');
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/measurements/measurement_category.dart';
|
||||
@@ -101,31 +99,26 @@ class MeasurementCategoryForm extends StatelessWidget {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
categoryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addCategory(
|
||||
MeasurementCategory(
|
||||
id: categoryData['id'],
|
||||
name: categoryData['name'],
|
||||
unit: categoryData['unit'],
|
||||
),
|
||||
)
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editCategory(
|
||||
categoryData['id'],
|
||||
categoryData['name'],
|
||||
categoryData['unit'],
|
||||
);
|
||||
} on WgerHttpException catch (error) {
|
||||
if (context.mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
}
|
||||
categoryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addCategory(
|
||||
MeasurementCategory(
|
||||
id: categoryData['id'],
|
||||
name: categoryData['name'],
|
||||
unit: categoryData['unit'],
|
||||
),
|
||||
)
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editCategory(
|
||||
categoryData['id'],
|
||||
categoryData['name'],
|
||||
categoryData['unit'],
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
@@ -272,33 +265,28 @@ class MeasurementEntryForm extends StatelessWidget {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
_entryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addEntry(MeasurementEntry(
|
||||
id: _entryData['id'],
|
||||
category: _entryData['category'],
|
||||
date: _entryData['date'],
|
||||
value: _entryData['value'],
|
||||
notes: _entryData['notes'],
|
||||
))
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editEntry(
|
||||
_entryData['id'],
|
||||
_entryData['category'],
|
||||
_entryData['value'],
|
||||
_entryData['notes'],
|
||||
_entryData['date'],
|
||||
);
|
||||
} on WgerHttpException catch (error) {
|
||||
if (context.mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
}
|
||||
_entryData['id'] == null
|
||||
? await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addEntry(MeasurementEntry(
|
||||
id: _entryData['id'],
|
||||
category: _entryData['category'],
|
||||
date: _entryData['date'],
|
||||
value: _entryData['value'],
|
||||
notes: _entryData['notes'],
|
||||
))
|
||||
: await Provider.of<MeasurementProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editEntry(
|
||||
_entryData['id'],
|
||||
_entryData['category'],
|
||||
_entryData['value'],
|
||||
_entryData['notes'],
|
||||
_entryData['date'],
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/nutrition/ingredient.dart';
|
||||
@@ -89,25 +87,22 @@ class MealForm extends StatelessWidget {
|
||||
ElevatedButton(
|
||||
key: const Key(SUBMIT_BUTTON_KEY_NAME),
|
||||
child: Text(AppLocalizations.of(context).save),
|
||||
onPressed: () async {
|
||||
onPressed: () {
|
||||
if (!_form.currentState!.validate()) {
|
||||
return;
|
||||
}
|
||||
_form.currentState!.save();
|
||||
|
||||
try {
|
||||
_meal.id == null
|
||||
? Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addMeal(_meal, _planId)
|
||||
: Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editMeal(_meal);
|
||||
} on WgerHttpException catch (error) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
_meal.id == null
|
||||
? Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addMeal(_meal, _planId)
|
||||
: Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editMeal(_meal);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@@ -399,20 +394,17 @@ class IngredientFormState extends State<IngredientForm> {
|
||||
_form.currentState!.save();
|
||||
_mealItem.ingredientId = int.parse(_ingredientIdController.text);
|
||||
|
||||
try {
|
||||
var date = DateTime.parse(_dateController.text);
|
||||
final tod = stringToTime(_timeController.text);
|
||||
date = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
tod.hour,
|
||||
tod.minute,
|
||||
);
|
||||
widget.onSave(context, _mealItem, date);
|
||||
} on WgerHttpException catch (error) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
var date = DateTime.parse(_dateController.text);
|
||||
final tod = stringToTime(_timeController.text);
|
||||
date = DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
tod.hour,
|
||||
tod.minute,
|
||||
);
|
||||
widget.onSave(context, _mealItem, date);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
@@ -664,35 +656,29 @@ class _PlanFormState extends State<PlanForm> {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Save to DB
|
||||
try {
|
||||
if (widget._plan.id != null) {
|
||||
await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
widget._plan = await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
NutritionalPlanScreen.routeName,
|
||||
arguments: widget._plan,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Saving was successful, reset the data
|
||||
_descriptionController.clear();
|
||||
} on WgerHttpException catch (error) {
|
||||
if (widget._plan.id != null) {
|
||||
await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).editPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
widget._plan = await Provider.of<NutritionPlansProvider>(
|
||||
context,
|
||||
listen: false,
|
||||
).addPlan(widget._plan);
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pushReplacementNamed(
|
||||
NutritionalPlanScreen.routeName,
|
||||
arguments: widget._plan,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Saving was successful, reset the data
|
||||
_descriptionController.clear();
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@@ -20,7 +20,6 @@ import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/gym_mode.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/exercises/exercise.dart';
|
||||
@@ -313,11 +312,10 @@ class _LogPageState extends State<LogPage> {
|
||||
curve: DEFAULT_ANIMATION_CURVE,
|
||||
);
|
||||
_isSaving = false;
|
||||
} on WgerHttpException catch (error) {
|
||||
if (mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
} on WgerHttpException {
|
||||
_isSaving = false;
|
||||
|
||||
rethrow;
|
||||
}
|
||||
},
|
||||
child:
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart' as provider;
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/helpers/misc.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
@@ -225,20 +223,14 @@ class _SessionPageState extends State<SessionPage> {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
if (widget._session.id == null) {
|
||||
await routinesProvider.addSession(widget._session, widget._routine.id!);
|
||||
} else {
|
||||
await routinesProvider.editSession(widget._session);
|
||||
}
|
||||
if (widget._session.id == null) {
|
||||
await routinesProvider.addSession(widget._session, widget._routine.id!);
|
||||
} else {
|
||||
await routinesProvider.editSession(widget._session);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} on WgerHttpException catch (error) {
|
||||
if (mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -19,9 +19,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:wger/exceptions/http_exception.dart';
|
||||
import 'package:wger/helpers/consts.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
import 'package:wger/helpers/json.dart';
|
||||
import 'package:wger/l10n/generated/app_localizations.dart';
|
||||
import 'package:wger/models/body_weight/weight_entry.dart';
|
||||
@@ -172,16 +170,10 @@ class WeightForm extends StatelessWidget {
|
||||
_form.currentState!.save();
|
||||
|
||||
// Save the entry on the server
|
||||
try {
|
||||
final provider = Provider.of<BodyWeightProvider>(context, listen: false);
|
||||
_weightEntry.id == null
|
||||
? await provider.addEntry(_weightEntry)
|
||||
: await provider.editEntry(_weightEntry);
|
||||
} on WgerHttpException catch (error) {
|
||||
if (context.mounted) {
|
||||
showHttpExceptionErrorDialog(error, context: context);
|
||||
}
|
||||
}
|
||||
final provider = Provider.of<BodyWeightProvider>(context, listen: false);
|
||||
_weightEntry.id == null
|
||||
? await provider.addEntry(_weightEntry)
|
||||
: await provider.editEntry(_weightEntry);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
@@ -146,7 +146,7 @@ void main() {
|
||||
));
|
||||
});
|
||||
|
||||
testWidgets('Login - wront username & password', (WidgetTester tester) async {
|
||||
testWidgets('Login - wrong username & password', (WidgetTester tester) async {
|
||||
// Arrange
|
||||
await tester.binding.setSurfaceSize(const Size(1080, 1920));
|
||||
tester.view.devicePixelRatio = 1.0;
|
||||
@@ -168,7 +168,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.textContaining('An Error Occurred'), findsOne);
|
||||
expect(find.textContaining('Non field errors'), findsOne);
|
||||
expect(find.textContaining('Username or password unknown'), findsOne);
|
||||
verify(mockClient.post(
|
||||
@@ -259,7 +258,6 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Assert
|
||||
expect(find.textContaining('An Error Occurred'), findsOne);
|
||||
expect(find.textContaining('This password is too common'), findsOne);
|
||||
expect(find.textContaining('This password is entirely numeric'), findsOne);
|
||||
expect(find.textContaining('This field must be unique'), findsOne);
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:wger/helpers/errors.dart';
|
||||
|
||||
void main() {
|
||||
group('extractErrors', () {
|
||||
testWidgets('Returns empty list when errors is null', (WidgetTester tester) async {
|
||||
final result = extractErrors(null);
|
||||
expect(result, isEmpty);
|
||||
});
|
||||
|
||||
testWidgets('Returns empty list when errors is empty', (WidgetTester tester) async {
|
||||
final result = extractErrors({});
|
||||
expect(result, isEmpty);
|
||||
@@ -19,17 +13,14 @@ void main() {
|
||||
final errors = {'error': 'Something went wrong'};
|
||||
|
||||
// Act
|
||||
final widgets = extractErrors(errors);
|
||||
final result = extractErrors(errors);
|
||||
|
||||
// Assert
|
||||
expect(widgets.length, 3, reason: 'Expected 3 widgets: header, message, and spacing');
|
||||
expect(result.length, 1, reason: 'Expected 1 error');
|
||||
expect(result[0].errorMessages.length, 1, reason: '1 error message');
|
||||
|
||||
final headerWidget = widgets[0] as Text;
|
||||
expect(headerWidget.data, 'Error');
|
||||
|
||||
final messageWidget = widgets[1] as Text;
|
||||
expect(messageWidget.data, 'Something went wrong');
|
||||
expect(widgets[2] is SizedBox, true);
|
||||
expect(result[0].key, 'Error');
|
||||
expect(result[0].errorMessages[0], 'Something went wrong');
|
||||
});
|
||||
|
||||
testWidgets('Processes list values correctly', (WidgetTester tester) async {
|
||||
@@ -39,19 +30,12 @@ void main() {
|
||||
};
|
||||
|
||||
// Act
|
||||
final widgets = extractErrors(errors);
|
||||
final result = extractErrors(errors);
|
||||
|
||||
// Assert
|
||||
expect(widgets.length, 4);
|
||||
|
||||
final headerWidget = widgets[0] as Text;
|
||||
expect(headerWidget.data, 'Validation error');
|
||||
|
||||
final messageWidget1 = widgets[1] as Text;
|
||||
expect(messageWidget1.data, 'Error 1');
|
||||
|
||||
final messageWidget2 = widgets[2] as Text;
|
||||
expect(messageWidget2.data, 'Error 2');
|
||||
expect(result[0].key, 'Validation error');
|
||||
expect(result[0].errorMessages[0], 'Error 1');
|
||||
expect(result[0].errorMessages[1], 'Error 2');
|
||||
});
|
||||
|
||||
testWidgets('Processes multiple error types correctly', (WidgetTester tester) async {
|
||||
@@ -62,20 +46,21 @@ void main() {
|
||||
};
|
||||
|
||||
// Act
|
||||
final widgets = extractErrors(errors);
|
||||
final result = extractErrors(errors);
|
||||
|
||||
// Assert
|
||||
expect(widgets.length, 7);
|
||||
expect(result.length, 2);
|
||||
final error1 = result[0];
|
||||
final error2 = result[1];
|
||||
|
||||
final textWidgets = widgets.whereType<Text>().toList();
|
||||
expect(textWidgets.map((w) => w.data).contains('Username'), true);
|
||||
expect(textWidgets.map((w) => w.data).contains('Password'), true);
|
||||
expect(textWidgets.map((w) => w.data).contains('Username is too boring'), true);
|
||||
expect(textWidgets.map((w) => w.data).contains('Username is too short'), true);
|
||||
expect(textWidgets.map((w) => w.data).contains('Password does not match'), true);
|
||||
expect(error1.key, 'Username');
|
||||
expect(error1.errorMessages.length, 2);
|
||||
expect(error1.errorMessages[0], 'Username is too boring');
|
||||
expect(error1.errorMessages[1], 'Username is too short');
|
||||
|
||||
final spacers = widgets.whereType<SizedBox>().toList();
|
||||
expect(spacers.length, 2);
|
||||
expect(error2.key, 'Password');
|
||||
expect(error2.errorMessages.length, 1);
|
||||
expect(error2.errorMessages[0], 'Password does not match');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user