From 123f64d0ebe8148656e8832b6300e427d25cfb7a Mon Sep 17 00:00:00 2001 From: Roland Geider Date: Tue, 11 May 2021 13:18:05 +0200 Subject: [PATCH] Add some tests for the picture gallery --- lib/l10n/app_en.arb | 5 +- lib/screens/gallery_screen.dart | 5 +- lib/widgets/gallery/overview.dart | 4 +- pubspec.lock | 9 ++- pubspec.yaml | 1 + test/gallery_screen_test.dart | 89 +++++++++++++++++++++ test/gallery_screen_test.mocks.dart | 116 ++++++++++++++++++++++++++++ test_data/gallery.dart | 52 +++++++++++++ 8 files changed, 276 insertions(+), 5 deletions(-) create mode 100644 test/gallery_screen_test.dart create mode 100644 test/gallery_screen_test.mocks.dart create mode 100644 test_data/gallery.dart diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index ded39427..d4f00b69 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -394,5 +394,8 @@ "description": "Label and error message when the user hasn't selected an image to save" }, "takePicture": "Take a picture", - "chooseFromLibrary": "Choose from photo library" + "chooseFromLibrary": "Choose from photo library", + "gallery": "Gallery", + "addImage": "Add image" + } diff --git a/lib/screens/gallery_screen.dart b/lib/screens/gallery_screen.dart index d4ee4d08..41bc4a73 100644 --- a/lib/screens/gallery_screen.dart +++ b/lib/screens/gallery_screen.dart @@ -20,6 +20,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:image_picker/image_picker.dart'; import 'package:provider/provider.dart'; import 'package:wger/providers/gallery.dart'; @@ -63,7 +64,7 @@ class _GalleryScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: WgerAppBar( - 'Gallery', + AppLocalizations.of(context)!.gallery, ), drawer: AppDrawer(), floatingActionButton: FloatingActionButton( @@ -73,7 +74,7 @@ class _GalleryScreenState extends State { context, FormScreen.routeName, arguments: FormScreenArguments( - 'Add image', + AppLocalizations.of(context)!.addImage, ImageForm(), true, ), diff --git a/lib/widgets/gallery/overview.dart b/lib/widgets/gallery/overview.dart index 58601fcc..6f25bd62 100644 --- a/lib/widgets/gallery/overview.dart +++ b/lib/widgets/gallery/overview.dart @@ -47,6 +47,7 @@ class Gallery extends StatelessWidget { showModalBottomSheet( builder: (context) => Material( child: Container( + key: Key('image-${currentImage.id}-detail'), padding: EdgeInsets.all(10), color: Colors.white, child: Column( @@ -96,8 +97,9 @@ class Gallery extends StatelessWidget { ); }, child: FadeInImage( + key: Key('image-${currentImage.id}'), placeholder: AssetImage('assets/images/placeholder.png'), - image: NetworkImage(provider.images[index].url!), + image: NetworkImage(currentImage.url!), fit: BoxFit.cover, ), ); diff --git a/pubspec.lock b/pubspec.lock index 9067ad9d..75e24b03 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -332,7 +332,7 @@ packages: name: flutter_math_fork url: "https://pub.dartlang.org" source: hosted - version: "0.3.2+1" + version: "0.3.3" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -518,6 +518,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + network_image_mock: + dependency: "direct dev" + description: + name: network_image_mock + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b016cf26..4aa92170 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,6 +58,7 @@ dev_dependencies: flutter_launcher_icons: ^0.9.0 json_serializable: ^4.0.4 mockito: ^5.0.4 + network_image_mock: ^2.0.0 flutter_icons: android: true diff --git a/test/gallery_screen_test.dart b/test/gallery_screen_test.dart new file mode 100644 index 00000000..ee0a4202 --- /dev/null +++ b/test/gallery_screen_test.dart @@ -0,0 +1,89 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:network_image_mock/network_image_mock.dart'; +import 'package:provider/provider.dart'; +import 'package:wger/providers/gallery.dart'; +import 'package:wger/widgets/gallery/overview.dart'; + +import '../test_data/gallery.dart'; +import 'gallery_screen_test.mocks.dart'; + +@GenerateMocks([GalleryProvider]) +void main() { + var mockGalleryProvider = MockGalleryProvider(); + + setUp(() { + mockGalleryProvider = MockGalleryProvider(); + when(mockGalleryProvider.images).thenAnswer((_) => getTestImages()); + }); + + Widget createScreen({locale = 'en'}) { + return ChangeNotifierProvider( + create: (context) => mockGalleryProvider, + child: MaterialApp( + locale: Locale(locale), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Gallery(), + ), + ); + } + + testWidgets('Test the widgets on the gallery screen', (WidgetTester tester) async { + await mockNetworkImagesFor(() => tester.pumpWidget(createScreen())); + + expect(find.byType(GestureDetector), findsNWidgets(4)); + }); + + testWidgets('Test opening the form for an existing image', (WidgetTester tester) async { + await mockNetworkImagesFor(() => tester.pumpWidget(createScreen())); + + await tester.tap(find.byKey(Key('image-1'))); + await tester.pumpAndSettle(); + + // Edit dialog opens + expect(find.byKey(Key('image-1-detail')), findsOneWidget); + expect(find.byType(Image), findsNWidgets(5)); // four in the overview, one in the popup + expect(find.text('A very cool image from the gym'), findsOneWidget); + expect(find.byIcon(Icons.edit), findsOneWidget); + expect(find.byIcon(Icons.delete), findsOneWidget); + //expect(find.byType(ListTile), findsOneWidget); + }); + + testWidgets('Tests the localization of dates - EN', (WidgetTester tester) async { + await mockNetworkImagesFor(() => tester.pumpWidget(createScreen())); + await tester.tap(find.byKey(Key('image-1'))); + await tester.pumpAndSettle(); + + expect(find.text('5/30/2021'), findsOneWidget); + }); + + testWidgets('Tests the localization of dates - DE', (WidgetTester tester) async { + await mockNetworkImagesFor(() => tester.pumpWidget(createScreen(locale: 'de'))); + await tester.tap(find.byKey(Key('image-1'))); + await tester.pumpAndSettle(); + + expect(find.text('30.5.2021'), findsOneWidget); + }); +} diff --git a/test/gallery_screen_test.mocks.dart b/test/gallery_screen_test.mocks.dart new file mode 100644 index 00000000..34949163 --- /dev/null +++ b/test/gallery_screen_test.mocks.dart @@ -0,0 +1,116 @@ +// Mocks generated by Mockito 5.0.7 from annotations +// in wger/test/gallery_screen_test.dart. +// Do not manually edit this file. + +import 'dart:async' as _i7; +import 'dart:ui' as _i9; + +import 'package:http/src/client.dart' as _i3; +import 'package:http/src/response.dart' as _i4; +import 'package:image_picker_platform_interface/src/types/picked_file/io.dart' as _i8; +import 'package:mockito/mockito.dart' as _i1; +import 'package:wger/models/gallery/image.dart' as _i6; +import 'package:wger/providers/auth.dart' as _i2; +import 'package:wger/providers/gallery.dart' as _i5; + +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis + +// ignore_for_file: prefer_const_constructors + +// ignore_for_file: avoid_redundant_argument_values + +class _FakeAuthProvider extends _i1.Fake implements _i2.AuthProvider {} + +class _FakeClient extends _i1.Fake implements _i3.Client {} + +class _FakeResponse extends _i1.Fake implements _i4.Response {} + +/// A class which mocks [GalleryProvider]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGalleryProvider extends _i1.Mock implements _i5.GalleryProvider { + MockGalleryProvider() { + _i1.throwOnMissingStub(this); + } + + @override + List<_i6.Image> get images => + (super.noSuchMethod(Invocation.getter(#images), returnValue: <_i6.Image>[]) + as List<_i6.Image>); + @override + set images(List<_i6.Image>? _images) => + super.noSuchMethod(Invocation.setter(#images, _images), returnValueForMissingStub: null); + @override + bool get hasListeners => + (super.noSuchMethod(Invocation.getter(#hasListeners), returnValue: false) as bool); + @override + _i2.AuthProvider get auth => + (super.noSuchMethod(Invocation.getter(#auth), returnValue: _FakeAuthProvider()) + as _i2.AuthProvider); + @override + set auth(_i2.AuthProvider? _auth) => + super.noSuchMethod(Invocation.setter(#auth, _auth), returnValueForMissingStub: null); + @override + _i3.Client get client => + (super.noSuchMethod(Invocation.getter(#client), returnValue: _FakeClient()) as _i3.Client); + @override + set client(_i3.Client? _client) => + super.noSuchMethod(Invocation.setter(#client, _client), returnValueForMissingStub: null); + @override + _i7.Future fetchAndSetGallery() => + (super.noSuchMethod(Invocation.method(#fetchAndSetGallery, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future addImage(_i6.Image? image, _i8.PickedFile? imageFile) => + (super.noSuchMethod(Invocation.method(#addImage, [image, imageFile]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future editImage(_i6.Image? image, _i8.PickedFile? imageFile) => + (super.noSuchMethod(Invocation.method(#editImage, [image, imageFile]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + _i7.Future deleteImage(_i6.Image? image) => + (super.noSuchMethod(Invocation.method(#deleteImage, [image]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super + .noSuchMethod(Invocation.method(#addListener, [listener]), returnValueForMissingStub: null); + @override + void removeListener(_i9.VoidCallback? listener) => + super.noSuchMethod(Invocation.method(#removeListener, [listener]), + returnValueForMissingStub: null); + @override + void dispose() => + super.noSuchMethod(Invocation.method(#dispose, []), returnValueForMissingStub: null); + @override + void notifyListeners() => + super.noSuchMethod(Invocation.method(#notifyListeners, []), returnValueForMissingStub: null); + @override + dynamic makeUrl(String? path, {int? id, String? objectMethod, Map? query}) => + super.noSuchMethod(Invocation.method( + #makeUrl, [path], {#id: id, #objectMethod: objectMethod, #query: query})); + @override + _i7.Future> fetch(Uri? uri) => + (super.noSuchMethod(Invocation.method(#fetch, [uri]), + returnValue: Future>.value({})) + as _i7.Future>); + @override + _i7.Future> post(Map? data, Uri? uri) => + (super.noSuchMethod(Invocation.method(#post, [data, uri]), + returnValue: Future>.value({})) + as _i7.Future>); + @override + _i7.Future> patch(Map? data, Uri? uri) => + (super.noSuchMethod(Invocation.method(#patch, [data, uri]), + returnValue: Future>.value({})) + as _i7.Future>); + @override + _i7.Future<_i4.Response> deleteRequest(String? url, int? id) => + (super.noSuchMethod(Invocation.method(#deleteRequest, [url, id]), + returnValue: Future<_i4.Response>.value(_FakeResponse())) as _i7.Future<_i4.Response>); +} diff --git a/test_data/gallery.dart b/test_data/gallery.dart new file mode 100644 index 00000000..32260fd0 --- /dev/null +++ b/test_data/gallery.dart @@ -0,0 +1,52 @@ +/* + * This file is part of wger Workout Manager . + * Copyright (C) 2020, 2021 wger Team + * + * wger Workout Manager is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * wger Workout Manager is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:wger/models/gallery/image.dart' as gallery; + +List getTestImages() { + return [ + gallery.Image( + id: 1, + url: + 'https://github.com/wger-project/flutter/raw/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/01%20-%20workout%20plan.png?raw=true', + description: 'A very cool image from the gym', + date: DateTime(2021, 5, 30), + ), + gallery.Image( + id: 2, + url: + 'https://github.com/wger-project/flutter/raw/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/02%20-%20workout%20log.png?raw=true', + description: 'Some description', + date: DateTime(2021, 4, 20), + ), + gallery.Image( + id: 3, + url: + 'https://github.com/wger-project/flutter/raw/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/04%20-%20nutritional%20plan.png?raw=true', + description: '1 22 333 4444', + date: DateTime(2021, 5, 30), + ), + gallery.Image( + id: 4, + url: + 'https://raw.githubusercontent.com/wger-project/flutter/master/android/fastlane/metadata/android/en-US/images/phoneScreenshots/05%20-%20weight.png', + description: '', + date: DateTime(2021, 2, 22), + ) + ]; +}