From ae73843d3c1c50b8c4ae124f2d75e6a220311ed4 Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Thu, 7 Dec 2023 16:43:53 -0500 Subject: [PATCH 01/10] add default quicklinks as remote config based --- .gitmodules | 4 ++ android/build.gradle | 2 +- font_awesome_flutter | 1 + lib/core/managers/quick_link_repository.dart | 49 +++++++++++++++- lib/core/models/quick_link.dart | 22 +++++-- .../services/course_grade_cache_service.dart | 57 +++++++++++++++++++ .../services/firebase_storage_service.dart | 34 +++++++++++ lib/core/services/remote_config_service.dart | 12 ++-- .../viewmodels/quick_links_viewmodel.dart | 6 +- lib/locator.dart | 2 + pubspec.lock | 35 +++++++++--- pubspec.yaml | 5 ++ .../managers/quick_links_repository_mock.dart | 2 +- 13 files changed, 209 insertions(+), 22 deletions(-) create mode 100644 .gitmodules create mode 160000 font_awesome_flutter create mode 100644 lib/core/services/course_grade_cache_service.dart create mode 100644 lib/core/services/firebase_storage_service.dart diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..62f4e4279 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "font_awesome_flutter"] + path = font_awesome_flutter + url = git@github.com:ApplETS/font_awesome_flutter.git + branch = ets-mobile diff --git a/android/build.gradle b/android/build.gradle index 914f28ee2..5128a2b48 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.5.31' + ext.kotlin_version = '1.9.21' repositories { google() mavenCentral() diff --git a/font_awesome_flutter b/font_awesome_flutter new file mode 160000 index 000000000..e8ceccbeb --- /dev/null +++ b/font_awesome_flutter @@ -0,0 +1 @@ +Subproject commit e8ceccbebd9ec7f614e5232defc900fe608d3310 diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index 3bc2392c5..02e764ef9 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -1,7 +1,11 @@ // Dart imports: import 'dart:convert'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:notredame/ui/utils/app_theme.dart'; +import 'package:font_awesome_flutter/name_icon_mapping.dart'; // Package imports: +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Project imports: @@ -9,10 +13,16 @@ import 'package:notredame/core/constants/quick_links.dart'; import 'package:notredame/core/managers/cache_manager.dart'; import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/models/quick_link_data.dart'; +import 'package:notredame/core/services/firebase_storage_service.dart'; import 'package:notredame/locator.dart'; +import 'package:notredame/core/services/remote_config_service.dart'; class QuickLinkRepository { final CacheManager _cacheManager = locator(); + final RemoteConfigService _remoteConfigService = + locator(); + final FirebaseStorageService _storageService = + locator(); static const String quickLinksCacheKey = "quickLinksCache"; @@ -36,7 +46,42 @@ class QuickLinkRepository { quickLinksCacheKey, jsonEncode(quickLinkDataList)); } - List getDefaultQuickLinks(AppIntl intl) { - return quickLinks(intl); + Future> getDefaultQuickLinks(AppIntl intl) async { + final values = await _remoteConfigService.quicklinks_values; + + if (values == null || values as List == null || (values as List).isEmpty) { + return quickLinks(intl); + } + final listValues = values as List; + final List listQuicklink = []; + + for (var i = 0; i < listValues.length; i++) { + Widget imageWidget; + Map map = listValues[i] as Map; + + if (map['icon'] != null) { + final String iconName = map['icon'] as String; + imageWidget = FaIcon( + faIconNameMapping['solid $iconName'], + color: AppTheme.etsLightRed, + size: 64, + ); + } else if (map['remotePath'] != null) { + final remoteUrl = + await _storageService.getImageUrl(map['remotePath'] as String); + imageWidget = Image.network( + remoteUrl, + color: AppTheme.etsLightRed, + ); + } + final QuickLink quickLink = + QuickLink.fromJson(listValues[i] as Map); + quickLink.name = + intl.localeName == "fr" ? quickLink.nameFr : quickLink.nameEn; + quickLink.image = imageWidget; + listQuicklink.add(quickLink); + } + + return listQuicklink; } } diff --git a/lib/core/models/quick_link.dart b/lib/core/models/quick_link.dart index 60a28c0e1..d4b7c2b1e 100644 --- a/lib/core/models/quick_link.dart +++ b/lib/core/models/quick_link.dart @@ -3,13 +3,27 @@ import 'package:flutter/material.dart'; class QuickLink { final int id; - final Widget image; - final String name; + Widget image; final String link; + final String nameFr; + final String nameEn; + String name; + // If name is provided it will be used instead of nameFr and nameEn QuickLink( {@required this.id, - @required this.image, - @required this.name, + this.image, + this.name, + this.nameFr, + this.nameEn, @required this.link}); + + factory QuickLink.fromJson(Map json) { + return QuickLink( + id: int.parse(json['id'] as String), + nameFr: json['nameFr'] as String, + nameEn: json['nameEn'] as String, + link: json['link'] as String, + ); + } } diff --git a/lib/core/services/course_grade_cache_service.dart b/lib/core/services/course_grade_cache_service.dart new file mode 100644 index 000000000..8b74cf829 --- /dev/null +++ b/lib/core/services/course_grade_cache_service.dart @@ -0,0 +1,57 @@ +// Package imports: +import 'package:firebase_analytics/firebase_analytics.dart'; + +/// Manage the analytics of the application +class CourseGradeCacheService { + final FirebaseAnalytics _analytics = FirebaseAnalytics.instance; + + CourseGradeCacheService(); + + void setCourseGradeCache(String semester, String courseCode, + String courseName, double courseGrade) { + _analytics.logEvent(name: "setCourseGradeCache"); + } +} + +class SemesterCache { + final String semester; + final List gradeCacheList; + + SemesterCache({this.semester, this.gradeCacheList}); + + factory SemesterCache.fromJson(Map json) { + return SemesterCache( + semester: json['semester'] as String, + gradeCacheList: (json['gradeCacheList'] as List) + .map((e) => GradeCache.fromJson(e as Map)) + .toList(), + ); + } + + Map toJson() => { + 'semester': semester, + 'gradeCacheList': gradeCacheList.map((e) => e.toJson()), + }; +} + +class GradeCache { + final String courseCode; + final String courseName; + final double courseGrade; + + GradeCache({this.courseCode, this.courseName, this.courseGrade}); + + factory GradeCache.fromJson(Map json) { + return GradeCache( + courseCode: json['courseCode'] as String, + courseName: json['courseName'] as String, + courseGrade: json['courseGrade'] as double, + ); + } + + Map toJson() => { + 'courseCode': courseCode, + 'courseName': courseName, + 'courseGrade': courseGrade, + }; +} diff --git a/lib/core/services/firebase_storage_service.dart b/lib/core/services/firebase_storage_service.dart new file mode 100644 index 000000000..bccbb9803 --- /dev/null +++ b/lib/core/services/firebase_storage_service.dart @@ -0,0 +1,34 @@ +// Package imports: +import 'package:firebase_storage/firebase_storage.dart'; + +// Project imports: +import 'package:notredame/core/services/analytics_service.dart'; +import 'package:notredame/locator.dart'; + +/// Manage the analytics of the application +class FirebaseStorageService { + static const String tag = "FirebaseStorageService"; + static const String _defaultPath = "app-images"; + + final FirebaseStorage _firebaseStorage = FirebaseStorage.instance; + + final AnalyticsService _analyticsService = locator(); + + /// Reference to the app images folder in default cloud storage + Reference _appImageReferences; + + /// Get the url of the image from the firebase storage + /// [fileName] is the fileName of the image in the firebase storage + Future getImageUrl(String fileName) async { + try { + _appImageReferences ??= _firebaseStorage.ref(_defaultPath); + final child = _appImageReferences.child(fileName); + final url = await child.getDownloadURL(); + return url; + } catch (e) { + _analyticsService.logError( + tag, "Error while getting the image url", e as Exception); + rethrow; + } + } +} diff --git a/lib/core/services/remote_config_service.dart b/lib/core/services/remote_config_service.dart index 163926af7..1b7087892 100644 --- a/lib/core/services/remote_config_service.dart +++ b/lib/core/services/remote_config_service.dart @@ -1,14 +1,12 @@ -//SERVICE - // Package imports: +import 'dart:convert'; + import 'package:firebase_remote_config/firebase_remote_config.dart'; // Project imports: import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/locator.dart'; -//OTHERS - /// Manage the analytics of the application class RemoteConfigService { static const String tag = "RemoteConfigService"; @@ -26,6 +24,7 @@ class RemoteConfigService { static const _dashboardMsgColor = "dashboard_message_color"; static const _dashboardMsgUrl = "dashboard_message_url"; static const _dashboardMsgType = "dashboard_message_type"; + static const _quicklinksValues = "quicklinks_values"; static const _scheduleListViewDefault = "schedule_list_view_default"; final FirebaseRemoteConfig _remoteConfig = FirebaseRemoteConfig.instance; @@ -108,6 +107,11 @@ class RemoteConfigService { return _remoteConfig.getString(_dashboardMsgType); } + Future get quicklinks_values async { + await fetch(); + return jsonDecode(_remoteConfig.getString(_quicklinksValues)); + } + Future fetch() async { final AnalyticsService analyticsService = locator(); try { diff --git a/lib/core/viewmodels/quick_links_viewmodel.dart b/lib/core/viewmodels/quick_links_viewmodel.dart index dfb2f6524..0af1f498f 100644 --- a/lib/core/viewmodels/quick_links_viewmodel.dart +++ b/lib/core/viewmodels/quick_links_viewmodel.dart @@ -34,7 +34,7 @@ class QuickLinksViewModel extends FutureViewModel> { // otherwise, return quickLinks according to the cache final defaultQuickLinks = - _quickLinkRepository.getDefaultQuickLinks(_appIntl); + await _quickLinkRepository.getDefaultQuickLinks(_appIntl); quickLinkDataList.sort((a, b) => a.index.compareTo(b.index)); return quickLinkDataList .map((data) => defaultQuickLinks @@ -47,8 +47,8 @@ class QuickLinksViewModel extends FutureViewModel> { final currentQuickLinkIds = quickLinkList.map((e) => e.id).toList(); // Return those not in current quick links but in default list - return _quickLinkRepository - .getDefaultQuickLinks(_appIntl) + final defaults = await _quickLinkRepository.getDefaultQuickLinks(_appIntl); + return defaults .where((element) => !currentQuickLinkIds.contains(element.id)) .toList(); } diff --git a/lib/locator.dart b/lib/locator.dart index ea8ee8336..108ac8108 100644 --- a/lib/locator.dart +++ b/lib/locator.dart @@ -12,6 +12,7 @@ import 'package:notredame/core/managers/settings_manager.dart'; import 'package:notredame/core/managers/user_repository.dart'; import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/core/services/app_widget_service.dart'; +import 'package:notredame/core/services/firebase_storage_service.dart'; import 'package:notredame/core/services/github_api.dart'; import 'package:notredame/core/services/in_app_review_service.dart'; import 'package:notredame/core/services/internal_info_service.dart'; @@ -35,6 +36,7 @@ void setupLocator() { locator.registerLazySingleton(() => const FlutterSecureStorage()); locator.registerLazySingleton(() => PreferencesService()); locator.registerLazySingleton(() => NetworkingService()); + locator.registerLazySingleton(() => FirebaseStorageService()); locator.registerLazySingleton(() => SirenFlutterService()); locator.registerLazySingleton(() => AppWidgetService()); locator.registerLazySingleton(() => InAppReviewService()); diff --git a/pubspec.lock b/pubspec.lock index 17314b6a4..9c45f1857 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,7 @@ packages: name: _flutterfire_internals url: "https://pub.dartlang.org" source: hosted - version: "1.0.12" + version: "1.3.7" analyzer: dependency: transitive description: @@ -324,21 +324,21 @@ packages: name: firebase_core url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.17.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "4.5.2" + version: "4.8.0" firebase_core_web: dependency: transitive description: name: firebase_core_web url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.8.0" firebase_crashlytics: dependency: "direct main" description: @@ -374,6 +374,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.18" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + url: "https://pub.dartlang.org" + source: hosted + version: "11.2.8" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.4.7" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.6.8" fixnum: dependency: transitive description: @@ -530,9 +551,9 @@ packages: font_awesome_flutter: dependency: "direct main" description: - name: font_awesome_flutter - url: "https://pub.dartlang.org" - source: hosted + path: font_awesome_flutter + relative: true + source: path version: "10.2.1" get_it: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index 51ecdbccd..17d19eb76 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: firebase_analytics: ^10.0.6 firebase_crashlytics: ^3.0.6 firebase_remote_config: ^3.0.6 + firebase_storage: ^11.0.6 # Utils logger: ^1.0.0 @@ -76,6 +77,10 @@ dependencies: import_sorter: ^4.6.0 flutter_keychain: ^2.4.0 +dependency_overrides: + font_awesome_flutter: + path: ./font_awesome_flutter + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/mock/managers/quick_links_repository_mock.dart b/test/mock/managers/quick_links_repository_mock.dart index 8cde2beb5..b57b70e46 100644 --- a/test/mock/managers/quick_links_repository_mock.dart +++ b/test/mock/managers/quick_links_repository_mock.dart @@ -26,7 +26,7 @@ class QuickLinkRepositoryMock extends Mock implements QuickLinkRepository { /// Stub the function [getDefaultQuickLinks] of [mock] when called will return [toReturn]. static void stubGetDefaultQuickLinks(QuickLinkRepositoryMock mock, {List toReturn = const []}) { - when(mock.getDefaultQuickLinks(any)).thenAnswer((_) => toReturn); + when(mock.getDefaultQuickLinks(any)).thenAnswer((_) async => toReturn); } /// Stub the function [updateQuickLinkDataToCache] of [mock] when called will complete without errors. From 9c7af75de7b29d0bf11e59e4df1b5587d360dc6b Mon Sep 17 00:00:00 2001 From: Samuel Montambault Date: Sun, 10 Dec 2023 12:50:05 -0500 Subject: [PATCH 02/10] use git version of packages --- .gitmodules | 4 ---- font_awesome_flutter | 1 - pubspec.lock | 8 +++++--- pubspec.yaml | 11 ++++++----- 4 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 .gitmodules delete mode 160000 font_awesome_flutter diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 62f4e4279..000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "font_awesome_flutter"] - path = font_awesome_flutter - url = git@github.com:ApplETS/font_awesome_flutter.git - branch = ets-mobile diff --git a/font_awesome_flutter b/font_awesome_flutter deleted file mode 160000 index e8ceccbeb..000000000 --- a/font_awesome_flutter +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e8ceccbebd9ec7f614e5232defc900fe608d3310 diff --git a/pubspec.lock b/pubspec.lock index 9c45f1857..55cef5076 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -551,9 +551,11 @@ packages: font_awesome_flutter: dependency: "direct main" description: - path: font_awesome_flutter - relative: true - source: path + path: "." + ref: ets-mobile + resolved-ref: e8ceccbebd9ec7f614e5232defc900fe608d3310 + url: "https://github.com/ApplETS/font_awesome_flutter.git" + source: git version: "10.2.1" get_it: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index 17d19eb76..6c8e42c2b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,12 @@ dependencies: url: https://github.com/ApplETS/ETS-API-Clients.git ref: 0.5.0 + font_awesome_flutter: + # path: ../font_awesome_flutter/ + git: + url: https://github.com/ApplETS/font_awesome_flutter.git + ref: ets-mobile + # Other http: ^0.13.4 flutter_cache_manager: ^3.0.1 @@ -63,7 +69,6 @@ dependencies: rive: ^0.9.1 connectivity_plus: ^4.0.2 flutter_svg: ^1.0.3 - font_awesome_flutter: ^10.2.1 in_app_review: ^2.0.6 device_info_plus: ^4.0.0 pub_semver: ^2.1.4 @@ -77,10 +82,6 @@ dependencies: import_sorter: ^4.6.0 flutter_keychain: ^2.4.0 -dependency_overrides: - font_awesome_flutter: - path: ./font_awesome_flutter - dev_dependencies: flutter_test: sdk: flutter From cdf63614a4d78c587f9ba513693ef8c9fa26bad7 Mon Sep 17 00:00:00 2001 From: Samuel Montambault Date: Sun, 10 Dec 2023 13:37:07 -0500 Subject: [PATCH 03/10] cache remote url fetching --- lib/core/managers/quick_link_repository.dart | 25 +++++++++++--- pubspec.lock | 35 ++++++++++++++++++++ pubspec.yaml | 1 + 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index 02e764ef9..b1766919a 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -1,5 +1,7 @@ // Dart imports: import 'dart:convert'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:notredame/ui/utils/app_theme.dart'; import 'package:font_awesome_flutter/name_icon_mapping.dart'; @@ -58,7 +60,6 @@ class QuickLinkRepository { for (var i = 0; i < listValues.length; i++) { Widget imageWidget; Map map = listValues[i] as Map; - if (map['icon'] != null) { final String iconName = map['icon'] as String; imageWidget = FaIcon( @@ -67,10 +68,24 @@ class QuickLinkRepository { size: 64, ); } else if (map['remotePath'] != null) { - final remoteUrl = - await _storageService.getImageUrl(map['remotePath'] as String); - imageWidget = Image.network( - remoteUrl, + // from cache + final String remotePathKey = map['remotePath'] as String; + String imageUrl; + try { + imageUrl = await _cacheManager.get(remotePathKey); + } on Exception catch (_) { + if (kDebugMode) { + print( + "Image not in cache, fetching from cloud storage: $remotePathKey"); + } + + imageUrl = + await _storageService.getImageUrl(map['remotePath'] as String); + _cacheManager.update(remotePathKey, imageUrl); + } + + imageWidget = CachedNetworkImage( + imageUrl: imageUrl, color: AppTheme.etsLightRed, ); } diff --git a/pubspec.lock b/pubspec.lock index 55cef5076..6e449391d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,6 +78,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "8.1.4" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.3" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" calendar_view: dependency: "direct main" description: @@ -407,6 +428,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_blurhash: + dependency: transitive + description: + name: flutter_blurhash + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" flutter_cache_manager: dependency: "direct main" description: @@ -781,6 +809,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" package_config: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 6c8e42c2b..52a27394b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -81,6 +81,7 @@ dependencies: reorderable_grid_view: ^2.2.6 import_sorter: ^4.6.0 flutter_keychain: ^2.4.0 + cached_network_image: ^3.2.3 dev_dependencies: flutter_test: From 5636200e8640179ed5e0f51462407fbe66dd21b0 Mon Sep 17 00:00:00 2001 From: Samuel Montambault Date: Sun, 10 Dec 2023 14:16:35 -0500 Subject: [PATCH 04/10] add some tests --- test/managers/quick_link_repository_test.dart | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index f3de245f0..d1ffbdff0 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -1,4 +1,5 @@ // Dart imports: +import 'dart:async'; import 'dart:convert'; // Flutter imports: @@ -7,14 +8,17 @@ import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Project imports: import 'package:notredame/core/managers/cache_manager.dart'; import 'package:notredame/core/managers/quick_link_repository.dart'; import 'package:notredame/core/models/quick_link.dart'; import 'package:notredame/core/models/quick_link_data.dart'; +import 'package:notredame/core/services/remote_config_service.dart'; import '../helpers.dart'; import '../mock/managers/cache_manager_mock.dart'; +import '../mock/services/remote_config_service_mock.dart'; void main() { CacheManager cacheManager; @@ -92,5 +96,70 @@ void main() { throwsA(isInstanceOf())); }); }); + + group("getDefaultQuickLinks", () { + AppIntl appIntl; + RemoteConfigServiceMock remoteConfigServiceMock; + + dynamic quicklinkRemoteConfigIconStub = [ + { + "id": 0, + "nameEn": "ETS Portal", + "nameFr": "Portail de l'ÉTS", + "icon": "home" + }, + ]; + + setUp(() async { + appIntl = await setupAppIntl(); + + remoteConfigServiceMock = + setupRemoteConfigServiceMock() as RemoteConfigServiceMock; + }); + + tearDown(() { + clearInteractions(remoteConfigServiceMock); + unregister(); + }); + + test("Trying to get quicklinks when remote config is inacessible.", + () async { + when(remoteConfigServiceMock.quicklinks_values).thenAnswer(null); + + final quickLinks = + await quickLinkRepository.getDefaultQuickLinks(appIntl); + + verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock).called(0); + verify(cacheManager).called(0); + expect(quickLinks, isInstanceOf>()); + expect(quickLinks.length, 9); + expect(quickLinks.length, 9); + for (int i = 0; i < quickLinks.length; i++) { + expect(quickLinks[i].id, i); + } + }); + + test( + "Trying to get quicklinks from remote config with one icon attribute.", + () async { + // Stub the remote config to have values for quicklinks + when(remoteConfigServiceMock.quicklinks_values) + .thenAnswer((_) async => quicklinkRemoteConfigIconStub); + + final quickLinks = + await quickLinkRepository.getDefaultQuickLinks(appIntl); + + verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock).called(0); + verify(cacheManager).called(0); + expect(quickLinks, isInstanceOf>()); + expect(quickLinks.length, 9); + expect(quickLinks.length, 9); + for (int i = 0; i < quickLinks.length; i++) { + expect(quickLinks[i].id, i); + } + }); + }); }); } From 320624a48eb94ceabc37274ddb1c24610ef9563e Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Sun, 10 Dec 2023 22:19:01 -0500 Subject: [PATCH 05/10] add tests for firebase storage --- .../services/firebase_storage_service.dart | 19 +++++-- test/helpers.dart | 12 +++++ test/mock/services/firebase_storage_mock.dart | 16 ++++++ .../firebase_storage_service_test.dart | 49 +++++++++++++++++++ 4 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 test/mock/services/firebase_storage_mock.dart create mode 100644 test/services/firebase_storage_service_test.dart diff --git a/lib/core/services/firebase_storage_service.dart b/lib/core/services/firebase_storage_service.dart index bccbb9803..21ace4398 100644 --- a/lib/core/services/firebase_storage_service.dart +++ b/lib/core/services/firebase_storage_service.dart @@ -1,5 +1,6 @@ // Package imports: import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/cupertino.dart'; // Project imports: import 'package:notredame/core/services/analytics_service.dart'; @@ -10,24 +11,32 @@ class FirebaseStorageService { static const String tag = "FirebaseStorageService"; static const String _defaultPath = "app-images"; - final FirebaseStorage _firebaseStorage = FirebaseStorage.instance; + FirebaseStorage firebaseStorage; final AnalyticsService _analyticsService = locator(); /// Reference to the app images folder in default cloud storage Reference _appImageReferences; + @visibleForTesting + Reference get appImageReferences => _appImageReferences; + + FirebaseStorageService({this.firebaseStorage}) { + firebaseStorage ??= FirebaseStorage.instance; + _appImageReferences = firebaseStorage.ref(_defaultPath); + } + /// Get the url of the image from the firebase storage /// [fileName] is the fileName of the image in the firebase storage Future getImageUrl(String fileName) async { try { - _appImageReferences ??= _firebaseStorage.ref(_defaultPath); + _appImageReferences ??= firebaseStorage.ref(_defaultPath); + final child = _appImageReferences.child(fileName); final url = await child.getDownloadURL(); return url; - } catch (e) { - _analyticsService.logError( - tag, "Error while getting the image url", e as Exception); + } on Exception catch (e) { + _analyticsService.logError(tag, "Error while getting the image url", e); rethrow; } } diff --git a/test/helpers.dart b/test/helpers.dart index 798cb6b61..6d4e23790 100644 --- a/test/helpers.dart +++ b/test/helpers.dart @@ -19,6 +19,7 @@ import 'package:notredame/core/managers/settings_manager.dart'; import 'package:notredame/core/managers/user_repository.dart'; import 'package:notredame/core/services/analytics_service.dart'; import 'package:notredame/core/services/app_widget_service.dart'; +import 'package:notredame/core/services/firebase_storage_service.dart'; import 'package:notredame/core/services/github_api.dart'; import 'package:notredame/core/services/in_app_review_service.dart'; import 'package:notredame/core/services/internal_info_service.dart'; @@ -37,6 +38,7 @@ import 'mock/managers/settings_manager_mock.dart'; import 'mock/managers/user_repository_mock.dart'; import 'mock/services/analytics_service_mock.dart'; import 'mock/services/app_widget_service_mock.dart'; +import 'mock/services/firebase_storage_mock.dart'; import 'mock/services/flutter_secure_storage_mock.dart'; import 'mock/services/github_api_mock.dart'; import 'mock/services/in_app_review_service_mock.dart'; @@ -338,6 +340,16 @@ QuickLinkRepository setupQuickLinkRepositoryMock() { return repository; } +/// Load a mock of the [FirebaseStorageService] +FirebaseStorageServiceMock setupFirebaseStorageServiceMock() { + unregister(); + final storage = FirebaseStorageServiceMock(); + + locator.registerSingleton(storage); + + return storage; +} + bool getCalendarViewEnabled() { final RemoteConfigService remoteConfigService = locator(); diff --git a/test/mock/services/firebase_storage_mock.dart b/test/mock/services/firebase_storage_mock.dart new file mode 100644 index 000000000..f64e8d312 --- /dev/null +++ b/test/mock/services/firebase_storage_mock.dart @@ -0,0 +1,16 @@ +// Package imports: +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:mockito/mockito.dart'; + +// Project imports: +import 'package:notredame/core/services/firebase_storage_service.dart'; + +/// Mock for the [FirebaseStorageService] +class FirebaseStorageServiceMock extends Mock + implements FirebaseStorageService {} + +/// Mock for the [FirebaseStorage] +class FirebaseStorageMock extends Mock implements FirebaseStorage {} + +/// Mock for the [Reference] +class ReferenceMock extends Mock implements Reference {} diff --git a/test/services/firebase_storage_service_test.dart b/test/services/firebase_storage_service_test.dart new file mode 100644 index 000000000..751c4b6af --- /dev/null +++ b/test/services/firebase_storage_service_test.dart @@ -0,0 +1,49 @@ +// Package imports: +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +// Project imports: +import 'package:notredame/core/services/firebase_storage_service.dart'; +import '../helpers.dart'; +import '../mock/services/firebase_storage_mock.dart'; + +void main() { + FirebaseStorage firebaseStorageMock; + ReferenceMock rootMock; + ReferenceMock childMock; + FirebaseStorageService service; + + SharedPreferences.setMockInitialValues({}); + TestWidgetsFlutterBinding.ensureInitialized(); + + group("FirebaseStorageService - ", () { + setUp(() async { + setupAnalyticsServiceMock(); + + rootMock = ReferenceMock(); + childMock = ReferenceMock(); + firebaseStorageMock = FirebaseStorageMock(); + when(firebaseStorageMock.ref("any-images")).thenReturn(rootMock); + + when(firebaseStorageMock.ref("any-images")).thenReturn(rootMock); + when(rootMock.child("test.png")).thenReturn(childMock); + when(childMock.getDownloadURL()) + .thenAnswer((_) => Future.value("test-url")); + + service = FirebaseStorageService(firebaseStorage: firebaseStorageMock); + }); + + group("getImageUrl - ", () { + test("get image url", () async { + final url = await service.getImageUrl("test.png"); + verify(firebaseStorageMock.ref("any-images")); + verify(rootMock.child("test.png")); + verify(childMock.getDownloadURL()); + expect(url, isNotNull); + expect(url, "test-url"); + }); + }); + }); +} From 57c8a5359490038b16fa8f950d2f888aff794aa3 Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Tue, 12 Dec 2023 10:41:34 -0500 Subject: [PATCH 06/10] add tests --- lib/core/managers/quick_link_repository.dart | 9 +- test/helpers.dart | 2 +- test/managers/quick_link_repository_test.dart | 138 ++++++++++++++---- .../firebase_storage_service_test.dart | 6 +- 4 files changed, 113 insertions(+), 42 deletions(-) diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index b1766919a..417a83a68 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -1,7 +1,6 @@ // Dart imports: import 'dart:convert'; import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:notredame/ui/utils/app_theme.dart'; import 'package:font_awesome_flutter/name_icon_mapping.dart'; @@ -74,13 +73,7 @@ class QuickLinkRepository { try { imageUrl = await _cacheManager.get(remotePathKey); } on Exception catch (_) { - if (kDebugMode) { - print( - "Image not in cache, fetching from cloud storage: $remotePathKey"); - } - - imageUrl = - await _storageService.getImageUrl(map['remotePath'] as String); + imageUrl = await _storageService.getImageUrl(remotePathKey); _cacheManager.update(remotePathKey, imageUrl); } diff --git a/test/helpers.dart b/test/helpers.dart index 6d4e23790..7358efe86 100644 --- a/test/helpers.dart +++ b/test/helpers.dart @@ -341,7 +341,7 @@ QuickLinkRepository setupQuickLinkRepositoryMock() { } /// Load a mock of the [FirebaseStorageService] -FirebaseStorageServiceMock setupFirebaseStorageServiceMock() { +FirebaseStorageService setupFirebaseStorageServiceMock() { unregister(); final storage = FirebaseStorageServiceMock(); diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index d1ffbdff0..d9bb97fa0 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -1,12 +1,13 @@ // Dart imports: -import 'dart:async'; import 'dart:convert'; // Flutter imports: +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; // Package imports: import 'package:flutter_test/flutter_test.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:mockito/mockito.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -18,23 +19,33 @@ import 'package:notredame/core/models/quick_link_data.dart'; import 'package:notredame/core/services/remote_config_service.dart'; import '../helpers.dart'; import '../mock/managers/cache_manager_mock.dart'; +import '../mock/services/firebase_storage_mock.dart'; import '../mock/services/remote_config_service_mock.dart'; void main() { + AppIntl appIntl; CacheManager cacheManager; QuickLinkRepository quickLinkRepository; + RemoteConfigServiceMock remoteConfigServiceMock; + FirebaseStorageServiceMock storageServiceMock; group("QuickLinkRepository - ", () { - setUp(() { + setUp(() async { // Setup needed services and managers cacheManager = setupCacheManagerMock(); - + setupAnalyticsServiceMock(); + appIntl = await setupAppIntl(); + remoteConfigServiceMock = + setupRemoteConfigServiceMock() as RemoteConfigServiceMock; + storageServiceMock = + setupFirebaseStorageServiceMock() as FirebaseStorageServiceMock; quickLinkRepository = QuickLinkRepository(); }); tearDown(() { clearInteractions(cacheManager); unregister(); + unregister(); }); group("getQuickLinkDataFromCache - ", () { @@ -98,45 +109,40 @@ void main() { }); group("getDefaultQuickLinks", () { - AppIntl appIntl; - RemoteConfigServiceMock remoteConfigServiceMock; - - dynamic quicklinkRemoteConfigIconStub = [ + final quicklinkRemoteConfigRemoteImageStub = [ { - "id": 0, + "id": "1", "nameEn": "ETS Portal", "nameFr": "Portail de l'ÉTS", - "icon": "home" + "remotePath": "ic_monets.png" }, ]; - setUp(() async { - appIntl = await setupAppIntl(); - - remoteConfigServiceMock = - setupRemoteConfigServiceMock() as RemoteConfigServiceMock; - }); - - tearDown(() { - clearInteractions(remoteConfigServiceMock); - unregister(); - }); + final quicklinkRemoteConfigIconStub = [ + { + "id": "1", + "nameEn": "Répertoire", + "nameFr": "Directory", + "icon": "address-book" + }, + ]; test("Trying to get quicklinks when remote config is inacessible.", () async { - when(remoteConfigServiceMock.quicklinks_values).thenAnswer(null); + when(remoteConfigServiceMock.quicklinks_values) + .thenAnswer((_) async => null); final quickLinks = await quickLinkRepository.getDefaultQuickLinks(appIntl); verify(remoteConfigServiceMock.quicklinks_values).called(1); - verify(remoteConfigServiceMock).called(0); - verify(cacheManager).called(0); + verifyNoMoreInteractions(remoteConfigServiceMock); + verifyNoMoreInteractions(cacheManager); expect(quickLinks, isInstanceOf>()); - expect(quickLinks.length, 9); + // There should have 9 quicklinks if none is found in remote config expect(quickLinks.length, 9); for (int i = 0; i < quickLinks.length; i++) { - expect(quickLinks[i].id, i); + expect(quickLinks[i].id, i + 1); } }); @@ -151,13 +157,85 @@ void main() { await quickLinkRepository.getDefaultQuickLinks(appIntl); verify(remoteConfigServiceMock.quicklinks_values).called(1); - verify(remoteConfigServiceMock).called(0); - verify(cacheManager).called(0); + verifyNoMoreInteractions(remoteConfigServiceMock); + verifyNoMoreInteractions(cacheManager); expect(quickLinks, isInstanceOf>()); - expect(quickLinks.length, 9); - expect(quickLinks.length, 9); + expect(quickLinks.length, 1); + expect(quickLinks[0].image, isInstanceOf()); + // Verify that the icon is the right one (Icon data, size and color) + expect((quickLinks[0].image as FaIcon).icon.codePoint, 62137); + expect((quickLinks[0].image as FaIcon).size, 64); + expect((quickLinks[0].image as FaIcon).color, + const Color.fromARGB(255, 239, 62, 69)); + for (int i = 0; i < quickLinks.length; i++) { + expect(quickLinks[i].id, i + 1); + } + }); + + test( + "Trying to get quicklinks from remote config with one remote_path attribute (cached).", + () async { + // Arrange + when(remoteConfigServiceMock.quicklinks_values) + .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); + const String url = "https://url.com"; + CacheManagerMock.stubGet( + cacheManager as CacheManagerMock, "ic_monets.png", url); + + // Act + final quickLinks = + await quickLinkRepository.getDefaultQuickLinks(appIntl); + + // Assert + verify(remoteConfigServiceMock.quicklinks_values).called(1); + verifyNoMoreInteractions(storageServiceMock); + + verify(cacheManager.get("ic_monets.png")).called(1); + + verifyNoMoreInteractions(remoteConfigServiceMock); + verifyNoMoreInteractions(cacheManager); + + expect(quickLinks, isInstanceOf>()); + expect(quickLinks.length, 1); + expect(quickLinks[0].image, isInstanceOf()); + for (int i = 0; i < quickLinks.length; i++) { + expect(quickLinks[i].id, i + 1); + } + }); + + test( + "Trying to get quicklinks from remote config with one remote_path attribute (not previously cached).", + () async { + // Arrange + when(remoteConfigServiceMock.quicklinks_values) + .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); + const String url = "https://url.com"; + CacheManagerMock.stubGetException( + cacheManager as CacheManagerMock, "ic_monets.png"); + when(storageServiceMock.getImageUrl("ic_monets.png")) + .thenAnswer((_) async => url); + + // Act + final quickLinks = + await quickLinkRepository.getDefaultQuickLinks(appIntl); + + // Assert + verify(remoteConfigServiceMock.quicklinks_values).called(1); + verifyNoMoreInteractions(remoteConfigServiceMock); + + verify(cacheManager.get("ic_monets.png")).called(1); + + verify(storageServiceMock.getImageUrl("ic_monets.png")).called(1); + verify(cacheManager.update("ic_monets.png", url)).called(1); + verifyNoMoreInteractions(storageServiceMock); + verifyNoMoreInteractions(cacheManager); + + expect(quickLinks, isInstanceOf>()); + expect(quickLinks.length, 1); + expect(quickLinks[0].image, isInstanceOf()); + expect((quickLinks[0].image as CachedNetworkImage).imageUrl, url); for (int i = 0; i < quickLinks.length; i++) { - expect(quickLinks[i].id, i); + expect(quickLinks[i].id, i + 1); } }); }); diff --git a/test/services/firebase_storage_service_test.dart b/test/services/firebase_storage_service_test.dart index 751c4b6af..96eafd43e 100644 --- a/test/services/firebase_storage_service_test.dart +++ b/test/services/firebase_storage_service_test.dart @@ -25,9 +25,9 @@ void main() { rootMock = ReferenceMock(); childMock = ReferenceMock(); firebaseStorageMock = FirebaseStorageMock(); - when(firebaseStorageMock.ref("any-images")).thenReturn(rootMock); + when(firebaseStorageMock.ref("app-images")).thenReturn(rootMock); - when(firebaseStorageMock.ref("any-images")).thenReturn(rootMock); + when(firebaseStorageMock.ref("app-images")).thenReturn(rootMock); when(rootMock.child("test.png")).thenReturn(childMock); when(childMock.getDownloadURL()) .thenAnswer((_) => Future.value("test-url")); @@ -38,7 +38,7 @@ void main() { group("getImageUrl - ", () { test("get image url", () async { final url = await service.getImageUrl("test.png"); - verify(firebaseStorageMock.ref("any-images")); + verify(firebaseStorageMock.ref("app-images")); verify(rootMock.child("test.png")); verify(childMock.getDownloadURL()); expect(url, isNotNull); From 4dd90775af6293fa2a408d2949c8511b43eba70e Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Tue, 12 Dec 2023 16:21:41 +0000 Subject: [PATCH 07/10] [BOT] Applying version. --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 52a27394b..af1ab4beb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ description: The 4th generation of ÉTSMobile, the main gateway between the Éco # pub.dev using `pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 4.33.1+1 +version: 4.34.0+1 environment: sdk: ">=2.10.0 <3.0.0" From d63f5f9f7f6b4d34cbf17ca3b671718275381a9a Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Tue, 12 Dec 2023 20:59:38 -0500 Subject: [PATCH 08/10] fix analyse --- lib/core/managers/quick_link_repository.dart | 4 ++-- lib/core/services/remote_config_service.dart | 2 +- test/managers/quick_link_repository_test.dart | 16 ++++++++-------- test/mock/services/firebase_storage_mock.dart | 2 ++ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index 417a83a68..66cf91fe1 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -48,7 +48,7 @@ class QuickLinkRepository { } Future> getDefaultQuickLinks(AppIntl intl) async { - final values = await _remoteConfigService.quicklinks_values; + final values = await _remoteConfigService.quicklinksValues; if (values == null || values as List == null || (values as List).isEmpty) { return quickLinks(intl); @@ -58,7 +58,7 @@ class QuickLinkRepository { for (var i = 0; i < listValues.length; i++) { Widget imageWidget; - Map map = listValues[i] as Map; + final map = listValues[i] as Map; if (map['icon'] != null) { final String iconName = map['icon'] as String; imageWidget = FaIcon( diff --git a/lib/core/services/remote_config_service.dart b/lib/core/services/remote_config_service.dart index 1b7087892..8778371ae 100644 --- a/lib/core/services/remote_config_service.dart +++ b/lib/core/services/remote_config_service.dart @@ -107,7 +107,7 @@ class RemoteConfigService { return _remoteConfig.getString(_dashboardMsgType); } - Future get quicklinks_values async { + Future get quicklinksValues async { await fetch(); return jsonDecode(_remoteConfig.getString(_quicklinksValues)); } diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index d9bb97fa0..6e5255f67 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -129,13 +129,13 @@ void main() { test("Trying to get quicklinks when remote config is inacessible.", () async { - when(remoteConfigServiceMock.quicklinks_values) + when(remoteConfigServiceMock.quicklinksValues) .thenAnswer((_) async => null); final quickLinks = await quickLinkRepository.getDefaultQuickLinks(appIntl); - verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(remoteConfigServiceMock); verifyNoMoreInteractions(cacheManager); expect(quickLinks, isInstanceOf>()); @@ -150,13 +150,13 @@ void main() { "Trying to get quicklinks from remote config with one icon attribute.", () async { // Stub the remote config to have values for quicklinks - when(remoteConfigServiceMock.quicklinks_values) + when(remoteConfigServiceMock.quicklinksValues) .thenAnswer((_) async => quicklinkRemoteConfigIconStub); final quickLinks = await quickLinkRepository.getDefaultQuickLinks(appIntl); - verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(remoteConfigServiceMock); verifyNoMoreInteractions(cacheManager); expect(quickLinks, isInstanceOf>()); @@ -176,7 +176,7 @@ void main() { "Trying to get quicklinks from remote config with one remote_path attribute (cached).", () async { // Arrange - when(remoteConfigServiceMock.quicklinks_values) + when(remoteConfigServiceMock.quicklinksValues) .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); const String url = "https://url.com"; CacheManagerMock.stubGet( @@ -187,7 +187,7 @@ void main() { await quickLinkRepository.getDefaultQuickLinks(appIntl); // Assert - verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(storageServiceMock); verify(cacheManager.get("ic_monets.png")).called(1); @@ -207,7 +207,7 @@ void main() { "Trying to get quicklinks from remote config with one remote_path attribute (not previously cached).", () async { // Arrange - when(remoteConfigServiceMock.quicklinks_values) + when(remoteConfigServiceMock.quicklinksValues) .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); const String url = "https://url.com"; CacheManagerMock.stubGetException( @@ -220,7 +220,7 @@ void main() { await quickLinkRepository.getDefaultQuickLinks(appIntl); // Assert - verify(remoteConfigServiceMock.quicklinks_values).called(1); + verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(remoteConfigServiceMock); verify(cacheManager.get("ic_monets.png")).called(1); diff --git a/test/mock/services/firebase_storage_mock.dart b/test/mock/services/firebase_storage_mock.dart index f64e8d312..d3fe1e60f 100644 --- a/test/mock/services/firebase_storage_mock.dart +++ b/test/mock/services/firebase_storage_mock.dart @@ -10,7 +10,9 @@ class FirebaseStorageServiceMock extends Mock implements FirebaseStorageService {} /// Mock for the [FirebaseStorage] +// ignore: avoid_implementing_value_types class FirebaseStorageMock extends Mock implements FirebaseStorage {} /// Mock for the [Reference] +// ignore: avoid_implementing_value_types class ReferenceMock extends Mock implements Reference {} From 04b6ea3d001d1f4b55ccdfb0a9f754eb6ecf5c1f Mon Sep 17 00:00:00 2001 From: MysticFragilist Date: Wed, 13 Dec 2023 12:43:52 -0500 Subject: [PATCH 09/10] clean up tests --- test/managers/quick_link_repository_test.dart | 24 +++++++++++-------- .../services/remote_config_service_mock.dart | 5 ++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index 6e5255f67..bd77b5d8a 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -129,12 +129,15 @@ void main() { test("Trying to get quicklinks when remote config is inacessible.", () async { - when(remoteConfigServiceMock.quicklinksValues) - .thenAnswer((_) async => null); + // Arrange + // Stub the remote config to return null + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock); + // Act final quickLinks = await quickLinkRepository.getDefaultQuickLinks(appIntl); + // Assert verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(remoteConfigServiceMock); verifyNoMoreInteractions(cacheManager); @@ -149,13 +152,14 @@ void main() { test( "Trying to get quicklinks from remote config with one icon attribute.", () async { + // Arrange // Stub the remote config to have values for quicklinks - when(remoteConfigServiceMock.quicklinksValues) - .thenAnswer((_) async => quicklinkRemoteConfigIconStub); - + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: quicklinkRemoteConfigIconStub); + // Act final quickLinks = await quickLinkRepository.getDefaultQuickLinks(appIntl); - + // Assert verify(remoteConfigServiceMock.quicklinksValues).called(1); verifyNoMoreInteractions(remoteConfigServiceMock); verifyNoMoreInteractions(cacheManager); @@ -176,8 +180,8 @@ void main() { "Trying to get quicklinks from remote config with one remote_path attribute (cached).", () async { // Arrange - when(remoteConfigServiceMock.quicklinksValues) - .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: quicklinkRemoteConfigRemoteImageStub); const String url = "https://url.com"; CacheManagerMock.stubGet( cacheManager as CacheManagerMock, "ic_monets.png", url); @@ -207,8 +211,8 @@ void main() { "Trying to get quicklinks from remote config with one remote_path attribute (not previously cached).", () async { // Arrange - when(remoteConfigServiceMock.quicklinksValues) - .thenAnswer((_) async => quicklinkRemoteConfigRemoteImageStub); + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: quicklinkRemoteConfigRemoteImageStub); const String url = "https://url.com"; CacheManagerMock.stubGetException( cacheManager as CacheManagerMock, "ic_monets.png"); diff --git a/test/mock/services/remote_config_service_mock.dart b/test/mock/services/remote_config_service_mock.dart index f4eea8f4b..3efb9d0ac 100644 --- a/test/mock/services/remote_config_service_mock.dart +++ b/test/mock/services/remote_config_service_mock.dart @@ -56,4 +56,9 @@ class RemoteConfigServiceMock extends Mock implements RemoteConfigService { {String toReturn = "https://clubapplets.ca/"}) { when(mock.dashboardMsgUrl).thenReturn(toReturn); } + + static void stubGetQuickLinks(RemoteConfigServiceMock mock, + {dynamic toReturn}) { + when(mock.quicklinksValues).thenAnswer((_) async => toReturn); + } } From 2040cb2fdfb3c305899138e2e52da5536b269547 Mon Sep 17 00:00:00 2001 From: Samuel Montambault Date: Fri, 12 Apr 2024 01:46:00 -0400 Subject: [PATCH 10/10] Update pubspec.yaml --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index e35ba54c0..c6a79f35d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,7 +71,6 @@ dependencies: flutter_svg: ^2.0.9 in_app_review: ^2.0.9 device_info_plus: ^9.1.2 - font_awesome_flutter: ^10.7.0 pub_semver: ^2.1.4 home_widget: ^0.4.1 marquee: ^2.2.3