diff --git a/lib/core/managers/quick_link_repository.dart b/lib/core/managers/quick_link_repository.dart index 3bc2392c5..66cf91fe1 100644 --- a/lib/core/managers/quick_link_repository.dart +++ b/lib/core/managers/quick_link_repository.dart @@ -1,7 +1,12 @@ // Dart imports: import 'dart:convert'; +import 'package:cached_network_image/cached_network_image.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'; // Package imports: +import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; // Project imports: @@ -9,10 +14,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 +47,49 @@ class QuickLinkRepository { quickLinksCacheKey, jsonEncode(quickLinkDataList)); } - List getDefaultQuickLinks(AppIntl intl) { - return quickLinks(intl); + Future> getDefaultQuickLinks(AppIntl intl) async { + final values = await _remoteConfigService.quicklinksValues; + + 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; + final 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) { + // from cache + final String remotePathKey = map['remotePath'] as String; + String imageUrl; + try { + imageUrl = await _cacheManager.get(remotePathKey); + } on Exception catch (_) { + imageUrl = await _storageService.getImageUrl(remotePathKey); + _cacheManager.update(remotePathKey, imageUrl); + } + + imageWidget = CachedNetworkImage( + imageUrl: imageUrl, + 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 635a8fc3f..4bfca364d 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..21ace4398 --- /dev/null +++ b/lib/core/services/firebase_storage_service.dart @@ -0,0 +1,43 @@ +// Package imports: +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/cupertino.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"; + + 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); + + final child = _appImageReferences.child(fileName); + final url = await child.getDownloadURL(); + return url; + } on Exception catch (e) { + _analyticsService.logError(tag, "Error while getting the image url", e); + rethrow; + } + } +} diff --git a/lib/core/services/remote_config_service.dart b/lib/core/services/remote_config_service.dart index b62ff0ca6..fc8c515ff 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 quicklinksValues 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 3ac13074f..83701cb5e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -129,6 +129,27 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.2" + 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: @@ -426,6 +447,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.25" + 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: @@ -439,6 +481,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: @@ -934,6 +983,13 @@ packages: url: "https://pub.dev" 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 5512044a5..c6a79f35d 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.38.0+1 +version: 4.39.0+1 environment: sdk: ">=3.3.0 <4.0.0" @@ -26,6 +26,7 @@ dependencies: firebase_analytics: ^10.0.6 firebase_crashlytics: ^3.0.6 firebase_remote_config: ^4.3.13 + firebase_storage: ^11.0.6 # Utils logger: ^2.2.0 @@ -47,6 +48,12 @@ dependencies: url: https://github.com/ApplETS/ETS-API-Clients.git ref: 1.0.0 + font_awesome_flutter: + # path: ../font_awesome_flutter/ + git: + url: https://github.com/ApplETS/font_awesome_flutter.git + ref: ets-mobile + # Other http: ^1.2.0 flutter_cache_manager: ^3.0.1 @@ -62,7 +69,6 @@ dependencies: rive: ^0.13.1 connectivity_plus: ^6.0.1 flutter_svg: ^2.0.9 - font_awesome_flutter: ^10.7.0 in_app_review: ^2.0.9 device_info_plus: ^9.1.2 pub_semver: ^2.1.4 @@ -75,6 +81,7 @@ dependencies: reorderable_grid_view: ^2.2.8 import_sorter: ^4.6.0 flutter_keychain: ^2.4.0 + cached_network_image: ^3.2.3 dev_dependencies: flutter_test: diff --git a/test/helpers.dart b/test/helpers.dart index 9bf0a4bc6..b145c3d9e 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 @@ QuickLinkRepositoryMock setupQuickLinkRepositoryMock() { return repository; } +/// Load a mock of the [FirebaseStorageService] +FirebaseStorageService setupFirebaseStorageServiceMock() { + unregister(); + final storage = FirebaseStorageServiceMock(); + + locator.registerSingleton(storage); + + return storage; +} + bool getCalendarViewEnabled() { final RemoteConfigService remoteConfigService = locator(); diff --git a/test/managers/quick_link_repository_test.dart b/test/managers/quick_link_repository_test.dart index b1bc3acdf..d782b09e0 100644 --- a/test/managers/quick_link_repository_test.dart +++ b/test/managers/quick_link_repository_test.dart @@ -2,36 +2,50 @@ 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'; // 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/firebase_storage_mock.dart'; +import '../mock/services/remote_config_service_mock.dart'; void main() { + late AppIntl appIntl; late CacheManagerMock cacheManagerMock; - late QuickLinkRepository quickLinkRepository; + late RemoteConfigServiceMock remoteConfigServiceMock; + late FirebaseStorageServiceMock storageServiceMock; group("QuickLinkRepository - ", () { - setUp(() { + setUp(() async { // Setup needed services and managers cacheManagerMock = setupCacheManagerMock(); - + setupAnalyticsServiceMock(); + appIntl = await setupAppIntl(); + remoteConfigServiceMock = + setupRemoteConfigServiceMock() as RemoteConfigServiceMock; + storageServiceMock = + setupFirebaseStorageServiceMock() as FirebaseStorageServiceMock; quickLinkRepository = QuickLinkRepository(); }); tearDown(() { clearInteractions(cacheManagerMock); unregister(); + unregister(); }); group("getQuickLinkDataFromCache - ", () { @@ -93,5 +107,141 @@ void main() { throwsA(isInstanceOf())); }); }); + + group("getDefaultQuickLinks", () { + final quicklinkRemoteConfigRemoteImageStub = [ + { + "id": "1", + "nameEn": "ETS Portal", + "nameFr": "Portail de l'ÉTS", + "remotePath": "ic_monets.png" + }, + ]; + + final quicklinkRemoteConfigIconStub = [ + { + "id": "1", + "nameEn": "Répertoire", + "nameFr": "Directory", + "icon": "address-book" + }, + ]; + + test("Trying to get quicklinks when remote config is inacessible.", + () async { + // 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); + expect(quickLinks, isInstanceOf>()); + // 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 + 1); + } + }); + + test( + "Trying to get quicklinks from remote config with one icon attribute.", + () async { + // Arrange + // Stub the remote config to have values for quicklinks + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: quicklinkRemoteConfigIconStub); + // Act + final quickLinks = + await quickLinkRepository.getDefaultQuickLinks(appIntl); + // Assert + verify(remoteConfigServiceMock.quicklinksValues).called(1); + verifyNoMoreInteractions(remoteConfigServiceMock); + verifyNoMoreInteractions(cacheManager); + expect(quickLinks, isInstanceOf>()); + 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 + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: 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.quicklinksValues).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 + RemoteConfigServiceMock.stubGetQuickLinks(remoteConfigServiceMock, + toReturn: 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.quicklinksValues).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 + 1); + } + }); + }); }); } diff --git a/test/mock/managers/quick_links_repository_mock.dart b/test/mock/managers/quick_links_repository_mock.dart index 44affe215..4924d43e8 100644 --- a/test/mock/managers/quick_links_repository_mock.dart +++ b/test/mock/managers/quick_links_repository_mock.dart @@ -29,7 +29,7 @@ class QuickLinkRepositoryMock extends MockQuickLinkRepository { /// 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. diff --git a/test/mock/services/firebase_storage_mock.dart b/test/mock/services/firebase_storage_mock.dart new file mode 100644 index 000000000..d3fe1e60f --- /dev/null +++ b/test/mock/services/firebase_storage_mock.dart @@ -0,0 +1,18 @@ +// 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] +// 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 {} diff --git a/test/mock/services/remote_config_service_mock.dart b/test/mock/services/remote_config_service_mock.dart index 1b518947e..52e63a22d 100644 --- a/test/mock/services/remote_config_service_mock.dart +++ b/test/mock/services/remote_config_service_mock.dart @@ -59,4 +59,9 @@ class RemoteConfigServiceMock extends MockRemoteConfigService { {String toReturn = "https://clubapplets.ca/"}) { when(mock.dashboardMsgUrl).thenReturn(toReturn); } + + static void stubGetQuickLinks(RemoteConfigServiceMock mock, + {dynamic toReturn}) { + when(mock.quicklinksValues).thenAnswer((_) async => toReturn); + } } diff --git a/test/services/firebase_storage_service_test.dart b/test/services/firebase_storage_service_test.dart new file mode 100644 index 000000000..96eafd43e --- /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("app-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")); + + service = FirebaseStorageService(firebaseStorage: firebaseStorageMock); + }); + + group("getImageUrl - ", () { + test("get image url", () async { + final url = await service.getImageUrl("test.png"); + verify(firebaseStorageMock.ref("app-images")); + verify(rootMock.child("test.png")); + verify(childMock.getDownloadURL()); + expect(url, isNotNull); + expect(url, "test-url"); + }); + }); + }); +}