diff --git a/lib/DOM/basic_user_info.dart b/lib/DOM/basic_user_info.dart index fe86002..998875d 100644 --- a/lib/DOM/basic_user_info.dart +++ b/lib/DOM/basic_user_info.dart @@ -1,4 +1,3 @@ -import 'package:firebase_auth/firebase_auth.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:open_fitness_tracker/DOM/training_metadata.dart'; diff --git a/lib/cloud_io/firestore_sync.dart b/lib/cloud_io/firestore_sync.dart index 91d03f5..6470f5d 100644 --- a/lib/cloud_io/firestore_sync.dart +++ b/lib/cloud_io/firestore_sync.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; -import 'package:flutter/foundation.dart'; import 'package:open_fitness_tracker/DOM/training_metadata.dart'; import 'package:open_fitness_tracker/DOM/basic_user_info.dart'; import 'package:open_fitness_tracker/navigation/routes.dart'; @@ -12,20 +11,30 @@ TODO is there a smoke test I can run on web/devices deploy? just want to see if the damn pages load & waht load times are like.. */ -CloudStorage cloudStorage = CloudStorage(); +late CloudStorage cloudStorage; // = CloudStorage(); class CloudStorage { - CloudStorage() { - _firestore.settings = const Settings( - persistenceEnabled: true, - cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED, - ); - // ignore: deprecated_member_use - // _firestore.enablePersistence( - // const PersistenceSettings(synchronizeTabs: true), - // ); + CloudStorage([FirebaseFirestore? fakeFirestore, FirebaseAuth? fakeFirebaseAuth]) { + if (fakeFirebaseAuth != null) { + firebaseAuth = fakeFirebaseAuth; + } else { + firebaseAuth = FirebaseAuth.instance; + } + if (fakeFirestore != null) { + firestore = fakeFirestore; + } else { + firestore = FirebaseFirestore.instance; + firestore.settings = const Settings( + persistenceEnabled: true, + cacheSizeBytes: Settings.CACHE_SIZE_UNLIMITED, + ); + // ignore: deprecated_member_use + // firestore.enablePersistence( + // const PersistenceSettings(synchronizeTabs: true), + // ); + } - FirebaseAuth.instance.userChanges().listen((User? user) { + firebaseAuth.userChanges().listen((User? user) { routerConfig.refresh(); //https://stackoverflow.com/a/77448906/3894291 if (user != null) { cloudStorage.refreshTrainingHistoryCacheIfItsBeenXHours(12); @@ -36,7 +45,8 @@ class CloudStorage { refreshTrainingHistoryCacheIfItsBeenXHours(12); } - final FirebaseFirestore _firestore = FirebaseFirestore.instance; + late final FirebaseFirestore firestore; // = FirebaseFirestore.instance; + late final FirebaseAuth firebaseAuth; // = FirebaseFirestore.instance; final _historyCacheClock = CollectionCacheUpdateClock(_historyKey); static const _historyKey = 'TrainingHistory'; @@ -58,12 +68,13 @@ class CloudStorage { if (retryCount >= maxRetries) { try { // Firestore operation + return await operation(); } on FirebaseException catch (e) { if (e.code == 'permission-denied') { // Handle permission error rethrow; } else if (e.code == 'unavailable') { - var user = FirebaseAuth.instance.currentUser; + var user = firebaseAuth.currentUser; int qq; // Handle network unavailable error } else { @@ -76,15 +87,14 @@ class CloudStorage { } } await Future.delayed(Duration(milliseconds: delay)); - delay *= 2; // Exponential backoff + delay *= 2; retryCount++; } } } bool isUserEmailVerified() { - return (FirebaseAuth.instance.currentUser != null && - FirebaseAuth.instance.currentUser!.emailVerified); + return (firebaseAuth.currentUser != null && firebaseAuth.currentUser!.emailVerified); } Future addTrainingSessionToHistory(TrainingSession session) async { @@ -93,9 +103,9 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } await _retryWithExponentialBackoff(() async { - await _firestore + await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .collection(_historyKey) .add(session.toJson()); }); @@ -110,10 +120,10 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } - final String userUid = FirebaseAuth.instance.currentUser!.uid; + final String userUid = firebaseAuth.currentUser!.uid; final String collectionPath = 'users/$userUid/$_historyKey'; - Query query = _firestore + Query query = firestore .collection(collectionPath) .orderBy('date', descending: true) .limit(limit); @@ -149,15 +159,15 @@ class CloudStorage { return await _retryWithExponentialBackoff(() async { QuerySnapshot cloudTrainingHistory; if (useCache) { - cloudTrainingHistory = await _firestore + cloudTrainingHistory = await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .collection(_historyKey) .get(const GetOptions(source: Source.cache)); } else { - cloudTrainingHistory = await _firestore + cloudTrainingHistory = await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .collection(_historyKey) .get(const GetOptions(source: Source.server)); _historyCacheClock.resetClock(); @@ -183,9 +193,9 @@ class CloudStorage { throw Exception("No training session ID! does this training session exist?"); } await _retryWithExponentialBackoff(() async { - await _firestore + await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .collection(_historyKey) .doc(sesh.id) .delete(); @@ -198,12 +208,12 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } await _retryWithExponentialBackoff(() async { - CollectionReference historyCollection = _firestore + CollectionReference historyCollection = firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .collection(_historyKey); QuerySnapshot snapshot = await historyCollection.get(); - WriteBatch batch = _firestore.batch(); + WriteBatch batch = firestore.batch(); for (DocumentSnapshot doc in snapshot.docs) { batch.delete(doc.reference); } @@ -217,9 +227,9 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } return await _retryWithExponentialBackoff(() async { - final docSnapshot = await _firestore + final docSnapshot = await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .get(const GetOptions(source: Source.server)); final data = docSnapshot.data() as Map?; @@ -238,9 +248,9 @@ class CloudStorage { "Sign in. Make sure to verify your email if not signing in with Google Sign In, etc..."); } await _retryWithExponentialBackoff(() async { - await _firestore + await firestore .collection('users') - .doc(FirebaseAuth.instance.currentUser!.uid) + .doc(firebaseAuth.currentUser!.uid) .update({_basicUserInfoKey: userInfo.toJson()}); }); } diff --git a/lib/community/community_page.dart b/lib/community/community_page.dart index fd11914..e4bf52c 100644 --- a/lib/community/community_page.dart +++ b/lib/community/community_page.dart @@ -1,6 +1,7 @@ import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider, AuthProvider; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart'; import 'package:open_fitness_tracker/common/common_widgets.dart'; import 'package:open_fitness_tracker/community/charts.dart'; import 'package:open_fitness_tracker/navigation/routes.dart'; @@ -31,10 +32,10 @@ class SignInOrProfileWidget extends StatefulWidget { class _SignInOrProfileWidgetState extends State { @override Widget build(BuildContext context) { - FirebaseAuth.instance.authStateChanges().listen((User? user) { + cloudStorage.firebaseAuth.authStateChanges().listen((User? user) { if (mounted) setState(() {}); }); - if (FirebaseAuth.instance.currentUser == null) { + if (cloudStorage.firebaseAuth.currentUser == null) { return Column( children: [ const Text( diff --git a/lib/community/profile_page.dart b/lib/community/profile_page.dart index 83ef151..054d519 100644 --- a/lib/community/profile_page.dart +++ b/lib/community/profile_page.dart @@ -6,6 +6,7 @@ import 'package:firebase_ui_oauth_google/firebase_ui_oauth_google.dart'; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart' hide EmailAuthProvider, AuthProvider; import 'package:go_router/go_router.dart'; +import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart'; import 'package:open_fitness_tracker/common/common_widgets.dart'; import 'package:open_fitness_tracker/firebase_options.dart'; import 'package:open_fitness_tracker/navigation/routes.dart'; @@ -57,7 +58,7 @@ class ProfileScreenWrapper extends StatelessWidget { : Container(), ), children: [ - if (!FirebaseAuth.instance.currentUser!.emailVerified) + if (!cloudStorage.firebaseAuth.currentUser!.emailVerified) MyGenericButton( onPressed: () { context.push(routeNames.VerifyEmail.text); @@ -77,6 +78,7 @@ class SignInScreenWrapper extends StatelessWidget { @override Widget build(BuildContext context) { return SignInScreen( + auth: cloudStorage.firebaseAuth, providers: providers, //I double check auth in routes.dart now, but...synergy. // I'm too dumb to get it to work only there. that's fine I think diff --git a/lib/main.dart b/lib/main.dart index aefd862..5b0e11a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; @@ -20,6 +21,7 @@ Future main() async { WidgetsFlutterBinding.ensureInitialized(); ExDB.init(); //TODO migrate to firestore & update rules. + // FirebaseApp firebase = await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); HydratedStorage hydratedStorage = await HydratedStorage.build( @@ -35,33 +37,38 @@ Future main() async { Timer? trainingDurationTimer; class MyApp extends StatelessWidget { - const MyApp({super.key}); + final FirebaseFirestore? fakeFirestore; + final FirebaseAuth? fakeFirebaseAuth; + const MyApp({ + this.fakeFirestore, + this.fakeFirebaseAuth, + super.key, + }); @override Widget build(BuildContext context) { return MultiBlocProvider( providers: [ - BlocProvider( - create: (_) => ExSearchCubit(), - ), - BlocProvider( - create: (_) => TrainingSessionCubit(), - ), - BlocProvider( - create: (_) => CreateNewExCubit(), - ), - BlocProvider( - create: (_) => BasicUserInfoCubit(), - ), + BlocProvider(create: (_) => ExSearchCubit()), + BlocProvider(create: (_) => TrainingSessionCubit()), + BlocProvider(create: (_) => CreateNewExCubit()), + BlocProvider(create: (_) => BasicUserInfoCubit()), ], child: Builder(builder: (context) { - trainingDurationTimer = Timer.periodic( - const Duration(seconds: 1), - (timer) { - context.read().updateDuration(); - }, - ); + //setup stoof: + if (fakeFirestore == null) { + //then this is not a smoke test. + // if we want to do bigger app tests..maybe we can enable this + trainingDurationTimer = Timer.periodic( + const Duration(seconds: 1), + (timer) { + context.read().updateDuration(); + }, + ); + } + cloudStorage = CloudStorage(fakeFirestore, fakeFirebaseAuth); + //and so we begin: return MaterialApp.router( theme: myTheme, routerConfig: routerConfig, diff --git a/lib/navigation/routes.dart b/lib/navigation/routes.dart index 3581842..6da5753 100644 --- a/lib/navigation/routes.dart +++ b/lib/navigation/routes.dart @@ -1,8 +1,8 @@ // ignore_for_file: constant_identifier_names, camel_case_types -import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:go_router/go_router.dart'; +import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart'; import 'package:open_fitness_tracker/community/community_page.dart'; import 'package:open_fitness_tracker/community/profile_page.dart'; import 'package:open_fitness_tracker/community/settings_page.dart'; @@ -102,10 +102,10 @@ final GoRouter routerConfig = GoRouter( //todo is this slow? // redirect to the login page if the user is not logged in redirect: (BuildContext context, GoRouterState state) async { - final bool loggedIn = FirebaseAuth.instance.currentUser != null; + final bool loggedIn = cloudStorage.firebaseAuth.currentUser != null; if (!loggedIn) { return routeNames.SignIn.text; - } else if (!FirebaseAuth.instance.currentUser!.emailVerified) { + } else if (!cloudStorage.firebaseAuth.currentUser!.emailVerified) { if (state.matchedLocation == routeNames.SignIn.text || state.matchedLocation == routeNames.Profile.text || state.matchedLocation == routeNames.VerifyEmail.text) { diff --git a/lib/navigation/scaffold_with_nav_bar.dart b/lib/navigation/scaffold_with_nav_bar.dart index ad453b0..0aec5f3 100644 --- a/lib/navigation/scaffold_with_nav_bar.dart +++ b/lib/navigation/scaffold_with_nav_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; +import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart'; import 'nav_bar_controller.dart'; class ScaffoldWithNavBar extends StatelessWidget { @@ -12,7 +13,7 @@ class ScaffoldWithNavBar extends StatelessWidget { @override Widget build(BuildContext context) { - User? user = FirebaseAuth.instance.currentUser; + User? user = cloudStorage.firebaseAuth.currentUser; bool isLoggedIn = user != null; return Scaffold( diff --git a/pubspec.yaml b/pubspec.yaml index 7b9eed5..a546c56 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,9 @@ dev_dependencies: json_serializable: ^6.7.1 build_runner: ^2.4.12 flutter_lints: ^4.0.0 + mocktail: ^1.0.4 + fake_cloud_firestore: ^3.0.3 + firebase_auth_mocks: ^0.14.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec diff --git a/scripts/ollama-test/bin/muscle_associator.dart b/scripts/ollama-test/bin/muscle_associator.dart index 0c9adc6..49f0424 100644 --- a/scripts/ollama-test/bin/muscle_associator.dart +++ b/scripts/ollama-test/bin/muscle_associator.dart @@ -3,8 +3,8 @@ import 'dart:convert'; import 'dart:io'; import 'package:ollama_dart/ollama_dart.dart'; - -//TODO run again with qwen..see if the shit is as good as ya had it the first time +//!!! +//todo run again with qwen..see if the shit is as good as ya had it the first time void main(List args) async { final file = File( diff --git a/test/widget_test.dart b/test/widget_test.dart index 25769fe..639165d 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,50 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - +import 'package:firebase_ui_auth/firebase_ui_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; - +import 'package:hydrated_bloc/hydrated_bloc.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:open_fitness_tracker/main.dart'; +import 'package:fake_cloud_firestore/fake_cloud_firestore.dart'; +import 'package:firebase_auth_mocks/firebase_auth_mocks.dart'; + +class MockStorage extends Mock implements Storage {} void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + TestWidgetsFlutterBinding.ensureInitialized(); + final firestore = FakeFirebaseFirestore(); + final firebaseAuth = MockFirebaseAuth(); + late Storage storage; - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + setUpAll(() async { + setFirebaseUiIsTestMode(true); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); + // Mock the HydratedBloc storage + storage = MockStorage(); + when(() => storage.write(any(), any())).thenAnswer((_) async {}); + when(() => storage.read(any())).thenReturn(null); + when(() => storage.delete(any())).thenAnswer((_) async {}); + when(() => storage.clear()).thenAnswer((_) async {}); - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Set the mocked storage for HydratedBloc + HydratedBloc.storage = storage; + }); + + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build your app and trigger a frame. + Key testKey = const Key("testKey"); + await tester.pumpWidget(MyApp( + key: testKey, + fakeFirestore: firestore, + fakeFirebaseAuth: firebaseAuth, + )); + + // expect(find.text('Register'), findsOneWidget); + // expect(find.byType(SignInScreenWrapper), findsOneWidget); + // expect(find.byType(Scaffold), findsOneWidget); + + await tester.pumpAndSettle(); + expect(find.byKey(testKey), findsAny); + expect(find.text('1'), findsNothing); + await tester.pumpAndSettle(); }); }