fix tests, introduce golden tests

to make golden tests work, we no longer plot the "now time" for "other logs"
this was pretty silly anyway...
This commit is contained in:
Dieter Plaetinck
2024-05-07 22:54:45 +02:00
parent e9d7a39ab9
commit 578d56a0df
13 changed files with 168 additions and 20 deletions

2
.gitignore vendored
View File

@@ -30,6 +30,8 @@
.pub-cache/
.pub/
/build/
**/failures/*.png
# Web related
lib/generated_plugin_registrant.dart

View File

@@ -64,7 +64,6 @@ class Meal {
this.mealItems = mealItems ?? [];
this.diaryEntries = diaryEntries ?? [];
time = time ?? TimeOfDay.fromDateTime(DateTime.now());
this.name = name ?? '';
}

View File

@@ -212,7 +212,6 @@ class NutritionalPlan {
id: PSEUDO_MEAL_ID,
plan: id,
name: name,
time: null,
diaryEntries: diaryEntries.where((e) => e.mealId == null).toList(),
);
}

View File

@@ -43,8 +43,8 @@ class FlNutritionalPlanGoalWidget extends StatefulWidget {
// even if it did, i doubt it would let us put text between the gauges/bars
// * LinearProgressIndicator has no way to visualize going beyond 100%, or
// using multiple colors to show multiple components such as surplus, deficit
// * here we try drawing our own simple gauges that can go beyond 100%,and we
// can use multiple colors...
// * here we draw our own simple gauges that can go beyond 100%,
// and support multiple segments
class FlNutritionalPlanGoalWidgetState extends State<FlNutritionalPlanGoalWidget> {
// normWidth is the width representing 100% completion
// note that if val > plan, we will draw beyond this width

View File

@@ -39,7 +39,7 @@ class MealForm extends StatelessWidget {
final _nameController = TextEditingController();
MealForm(this._planId, [meal]) {
_meal = meal ?? Meal(plan: _planId);
_meal = meal ?? Meal(plan: _planId, time: TimeOfDay.fromDateTime(DateTime.now()));
_timeController.text = timeToString(_meal.time)!;
_nameController.text = _meal.name;
}

View File

@@ -407,7 +407,7 @@ class MealHeader extends StatelessWidget {
],
)
: Text(
_meal.time!.format(context),
_meal.time != null ? _meal.time!.format(context) : '',
style: Theme.of(context).textTheme.headlineSmall,
),
),

View File

@@ -607,6 +607,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
golden_toolkit:
dependency: "direct dev"
description:
name: golden_toolkit
sha256: "8f74adab33154fe7b731395782797021f97d2edc52f7bfb85ff4f1b5c4a215f0"
url: "https://pub.dev"
source: hosted
version: "0.15.0"
graphs:
dependency: transitive
description:

View File

@@ -82,6 +82,7 @@ dev_dependencies:
cider: ^0.2.7
drift_dev: ^2.17.0
freezed: ^2.5.2
golden_toolkit: ^0.15.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -16,22 +16,112 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:http/http.dart' as http;
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:wger/models/nutrition/ingredient.dart';
import 'package:wger/providers/auth.dart';
import 'package:wger/providers/base_provider.dart';
import 'package:wger/providers/body_weight.dart';
import 'package:wger/providers/nutrition.dart';
import 'package:wger/screens/nutritional_plan_screen.dart';
import '../../test_data/nutritional_plans.dart';
import '../fixtures/fixture_reader.dart';
import 'helper.dart';
import 'nutritional_plan_screen_test.mocks.dart';
@GenerateMocks([WgerBaseProvider, AuthProvider, http.Client])
void main() {
// makeUrl('nutritionplan', {id: 1, objectMethod: null, query: null})
// Add a stub for this method using Mockito's 'when' API, or generate the MockWgerBaseProvider mock with the @GenerateNiceMocks annotation (see https://pub.dev/documentation/mockito/latest/annotations/MockSpec-class.html).)
late NutritionPlansProvider nutritionProvider;
late MockWgerBaseProvider mockWgerBaseProvider;
setUp(() {
mockWgerBaseProvider = MockWgerBaseProvider();
nutritionProvider = NutritionPlansProvider(mockWgerBaseProvider, []);
const String planInfoUrl = 'nutritionplaninfo';
const String planUrl = 'nutritionplan';
const String diaryUrl = 'nutritiondiary';
const String ingredientUrl = 'ingredient';
final Map<String, dynamic> nutritionalPlanInfoResponse = jsonDecode(
fixture('nutrition/nutritional_plan_info_detail_response.json'),
);
final Map<String, dynamic> nutritionalPlanDetailResponse = jsonDecode(
fixture('nutrition/nutritional_plan_detail_response.json'),
);
final List<dynamic> nutritionDiaryResponse = jsonDecode(
fixture('nutrition/nutrition_diary_response.json'),
)['results'];
final Map<String, dynamic> ingredient59887Response = jsonDecode(
fixture('nutrition/ingredient_59887_response.json'),
);
final Map<String, dynamic> ingredient10065Response = jsonDecode(
fixture('nutrition/ingredient_10065_response.json'),
);
final Map<String, dynamic> ingredient58300Response = jsonDecode(
fixture('nutrition/ingredient_58300_response.json'),
);
final ingredientList = [
Ingredient.fromJson(ingredient59887Response),
Ingredient.fromJson(ingredient10065Response),
Ingredient.fromJson(ingredient58300Response),
];
nutritionProvider.ingredients = ingredientList;
final Uri planInfoUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$planInfoUrl/1',
);
final Uri planUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$planUrl',
);
final Uri diaryUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$diaryUrl',
);
when(mockWgerBaseProvider.makeUrl(planInfoUrl, id: anyNamed('id'))).thenReturn(planInfoUri);
when(mockWgerBaseProvider.makeUrl(planUrl, id: anyNamed('id'))).thenReturn(planUri);
when(mockWgerBaseProvider.makeUrl(diaryUrl, query: anyNamed('query'))).thenReturn(diaryUri);
when(mockWgerBaseProvider.fetch(planInfoUri)).thenAnswer(
(realInvocation) => Future.value(nutritionalPlanInfoResponse),
);
when(mockWgerBaseProvider.fetch(planUri)).thenAnswer(
(realInvocation) => Future.value(nutritionalPlanDetailResponse),
);
when(mockWgerBaseProvider.fetchPaginated(diaryUri)).thenAnswer(
(realInvocation) => Future.value(nutritionDiaryResponse),
);
});
/*
late MockWgerBaseProvider mockBaseProvider;
// late ExercisesProvider provider;
const String npPlanUrl = 'nutritionplan';
final Uri tNpPlanEntriesUri = Uri(
scheme: 'http',
host: 'localhost',
path: 'api/v2/$npPlanUrl/',
);
*/
Widget createNutritionalPlan({locale = 'en'}) {
final key = GlobalKey<NavigatorState>();
final mockBaseProvider = MockWgerBaseProvider();
@@ -65,23 +155,70 @@ void main() {
);
}
testWidgets('Test the widgets on the nutritional plan screen', (WidgetTester tester) async {
await tester.pumpWidget(createNutritionalPlan());
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
testGoldens(
'Test the widgets on the nutritional plan screen',
(tester) async {
await loadAppFonts();
await tester.takeScreenshot(name: 'die1');
final globalKey = GlobalKey();
await tester.pumpWidgetBuilder(
Material(
key: globalKey,
),
wrapper: materialAppWrapper(
localizations: [
AppLocalizations.delegate,
],
),
surfaceSize: const Size(500, 1000),
);
await tester.pumpWidget(createNutritionalPlan());
await tester.tap(find.byType(TextButton));
await tester.pumpAndSettle();
// Plan description
expect(find.text('Less fat, more protein'), findsOneWidget);
await screenMatchesGolden(tester, 'nutritional_plan_1_default_view');
// Ingredients
expect(find.text('100g Water'), findsOneWidget);
expect(find.text('75g Burger soup'), findsOneWidget);
// the new goals widget pushes this content down a bit...
await tester.scrollUntilVisible(find.text('300g Broccoli cake'), 30);
expect(find.text('300g Broccoli cake'), findsOneWidget);
// Default view shows plan description, info button, and no ingredients
expect(find.text('Less fat, more protein'), findsOneWidget);
expect(find.byIcon(Icons.info_outline), findsNWidgets(3)); // 2 meals, 1 "other logs"
expect(find.byIcon(Icons.info), findsNothing);
expect(find.text('100g Water'), findsNothing);
expect(find.text('75g Burger soup'), findsNothing);
expect(find.byType(Card), findsNWidgets(2));
});
// tap the first info button changes it and reveals ingredients for the first meal
var infoOutlineButtons = find.byIcon(Icons.info_outline);
await tester.tap(infoOutlineButtons.first); // 2nd button shows up also, but is off-screen
await tester.pumpAndSettle();
await tester.takeScreenshot(name: 'die2');
await screenMatchesGolden(tester, 'nutritional_plan_2_one_meal_with_ingredients');
// Ingredients show up now
expect(find.text('100g Water'), findsOneWidget);
expect(find.text('75g Burger soup'), findsOneWidget);
// .. and the button icon has changed
expect(find.byIcon(Icons.info_outline), findsNWidgets(2));
expect(find.byIcon(Icons.info), findsOneWidget);
// the goals widget pushes this content down a bit.
// let's first find our icon (note: the previous icon no longer matches)
infoOutlineButtons = find.byIcon(Icons.info_outline);
await tester.scrollUntilVisible(infoOutlineButtons.first, 30);
expect(find.text('300g Broccoli cake'), findsNothing);
await tester.tap(infoOutlineButtons.first);
await tester.pumpAndSettle();
await screenMatchesGolden(tester, 'nutritional_plan_3_both_meals_with_ingredients');
expect(find.byIcon(Icons.info_outline), findsOneWidget);
expect(find.byIcon(Icons.info), findsNWidgets(2));
await tester.scrollUntilVisible(find.text('300g Broccoli cake'), 30);
expect(find.text('300g Broccoli cake'), findsOneWidget);
expect(find.byType(Card), findsNWidgets(3));
},
);
testWidgets('Tests the localization of times - EN', (WidgetTester tester) async {
await tester.pumpWidget(createNutritionalPlan());

2
test_data/dart_test.yaml Normal file
View File

@@ -0,0 +1,2 @@
tags:
golden: