diff --git a/packages/app/build.yaml b/packages/app/build.yaml index 27cd7843..0f816224 100644 --- a/packages/app/build.yaml +++ b/packages/app/build.yaml @@ -41,6 +41,8 @@ targets: - lib/src/utils/**.dart - lib/src/features/*/domain/*_model.dart - lib/src/features/*/domain/*_entity.dart + options: + checked: true riverpod_generator: # configs for the @riverpod generator generate_for: diff --git a/packages/app/lib/main.dart b/packages/app/lib/main.dart index fbf37f2b..695c378f 100644 --- a/packages/app/lib/main.dart +++ b/packages/app/lib/main.dart @@ -2,19 +2,16 @@ library; import 'package:flutter/widgets.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'src/app/app.dart'; import 'src/app/bootstrap.dart'; +import 'src/utils/api.dart'; /// The primary entrypoint of the app. /// /// This uses [Bootstrap] to launch [App]. Future main() async { - await const App().bootstrap( - ( - runApp: runApp, - getSharedPreferences: SharedPreferencesWithCache.create, - ), - ); + const env = (runApp: runApp, createClient: createClient); + + await const App().bootstrap(env); } diff --git a/packages/app/lib/src/app/bootstrap.dart b/packages/app/lib/src/app/bootstrap.dart index bce07ae3..1f473a2e 100644 --- a/packages/app/lib/src/app/bootstrap.dart +++ b/packages/app/lib/src/app/bootstrap.dart @@ -4,29 +4,27 @@ library; // `riverpod_lint` doesn't recognize that this is the root of the app. // ignore_for_file: scoped_providers_should_specify_dependencies +import 'package:appwrite/appwrite.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; import 'package:flutter_web_plugins/url_strategy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import '../features/settings/application/settings_service.dart'; import '../features/settings/data/preferences_repository.dart'; +import '../utils/api.dart'; /// The signature of [runApp]. typedef RunApp = void Function(Widget app); -/// The signature of [SharedPreferencesWithCache.create]. -typedef GetSharedPreferences = Future Function({ - required SharedPreferencesWithCacheOptions cacheOptions, - Map? cache, -}); +/// The signature of [createClient] +typedef CreateClient = Client Function(); /// The environment needed to bootstrap the app. typedef BootstrapEnv = ({ RunApp runApp, - GetSharedPreferences getSharedPreferences, + CreateClient createClient, }); /// Turn any widget into a flow-blown app. @@ -40,7 +38,7 @@ mixin Bootstrap implements Widget { /// - initializing riverpod's [ProviderScope], and /// - running the app with [runApp]. Future bootstrap(BootstrapEnv env) async { - final (:runApp, :getSharedPreferences) = env; + final (:runApp, :createClient) = env; // Don't use hash style routes. usePathUrlStrategy(); @@ -49,10 +47,9 @@ mixin Bootstrap implements Widget { WidgetsFlutterBinding.ensureInitialized(); // Load the user's preferences. - final prefs = await getSharedPreferences( - cacheOptions: const SharedPreferencesWithCacheOptions(), - ); - final initialSettings = await loadSettings(prefs); + final client = createClient(); + final account = Account(client); + final initialSettings = await loadSettings(account); // Reset splash screen. FlutterNativeSplash.remove(); @@ -65,7 +62,8 @@ mixin Bootstrap implements Widget { runApp( ProviderScope( overrides: [ - sharedPreferencesProvider.overrideWithValue(prefs), + clientProvider.overrideWithValue(client), + accountsProvider.overrideWithValue(account), initialSettingsProvider.overrideWithValue(initialSettings), ], child: RestorationScope( diff --git a/packages/app/lib/src/features/auth/application/auth_service.dart b/packages/app/lib/src/features/auth/application/auth_service.dart index 5a52844c..bc39faa1 100644 --- a/packages/app/lib/src/features/auth/application/auth_service.dart +++ b/packages/app/lib/src/features/auth/application/auth_service.dart @@ -22,9 +22,6 @@ base class AuthService extends _$AuthService { /// Creates a new user in the Appwrite database. Future createUser(String name, String email, String password) async { - // Set the state to loading. - state = const AsyncValue.loading(); - // Try to create the user. If it fails, set the state to error. // Note that expected errors are already converted to null. state = await AsyncValue.guard( diff --git a/packages/app/lib/src/features/auth/data/auth_repository.dart b/packages/app/lib/src/features/auth/data/auth_repository.dart index 1388f66c..8413300c 100644 --- a/packages/app/lib/src/features/auth/data/auth_repository.dart +++ b/packages/app/lib/src/features/auth/data/auth_repository.dart @@ -14,7 +14,7 @@ abstract interface class AuthRepository { /// Throws an [AppwriteException] if /// - the user already exists, or /// - the password is too weak. - Future createUser(String name, String email, String password); + Future createUser(String name, String email, String password); /// Log the user in. /// @@ -38,16 +38,20 @@ final class _AppwriteAuthRepository implements AuthRepository { final Account account; @override - Future createUser(String name, String email, String password) async { - final user = await account.create( - userId: ID.unique(), - email: email, - password: password, - name: name, - ); - await logInUser(email, password); + Future createUser(String name, String email, String password) async { + try { + final user = await account.create( + userId: ID.unique(), + email: email, + password: password, + name: name, + ); + await logInUser(email, password); - return user; + return user; + } on AppwriteException { + return null; + } } @override diff --git a/packages/app/lib/src/features/auth/presentation/auth/login_page.dart b/packages/app/lib/src/features/auth/presentation/auth/login_page.dart index 654c5c37..6c8cdb21 100644 --- a/packages/app/lib/src/features/auth/presentation/auth/login_page.dart +++ b/packages/app/lib/src/features/auth/presentation/auth/login_page.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../../app/router.gr.dart'; import '../../../../gen/assets.gen.dart'; +import '../../../../utils/toast.dart'; import '../../application/auth_service.dart'; // TODO(lishaduck): Rename to `LogInPage`. @@ -53,10 +54,8 @@ class LoginPage extends HookConsumerWidget { } } else { // TODO(lishaduck): Move this to the guard. - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid username and password'), - ), + context.showSnackBar( + content: const Text('Invalid username and password'), ); } } @@ -84,33 +83,18 @@ class LoginPage extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - // TODO(MattsAttack): Find a better color for this (use `Theme.of(context).`). - color: const Color.fromARGB( - 255, - 34, - 29, - 43, - ), + color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(15), ), child: Form( key: formKey, child: Column( children: [ - const DecoratedBox( - // TODO(MattsAttack): Redesign this, it was for testing. - decoration: BoxDecoration( - // color: Colors.white, - // border: Border.all(color: Colors.white), - // borderRadius: BorderRadius.circular(5), - ), - child: Text( - 'Welcome to Nexus!', - style: TextStyle( - fontSize: 28, - // TODO(MattsAttack): Use `Theme.of(context).`. - color: Color.fromARGB(255, 221, 168, 230), - ), + Text( + 'Welcome to Nexus!', + style: TextStyle( + fontSize: 28, + color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 32), diff --git a/packages/app/lib/src/features/auth/presentation/auth/signup_page.dart b/packages/app/lib/src/features/auth/presentation/auth/signup_page.dart index f9e3f438..8eda9b8c 100644 --- a/packages/app/lib/src/features/auth/presentation/auth/signup_page.dart +++ b/packages/app/lib/src/features/auth/presentation/auth/signup_page.dart @@ -5,6 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import '../../../../app/router.gr.dart'; import '../../../../gen/assets.gen.dart'; +import '../../../../utils/toast.dart'; import '../../application/auth_service.dart'; // TODO(lishaduck): Extract most of this out to a widget that can be shared with the log in page. @@ -54,10 +55,8 @@ class SignupPage extends HookConsumerWidget { } } else { // TODO(lishaduck): Move this to the guard. - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Invalid username or password'), - ), + context.showSnackBar( + content: const Text('Invalid username or password'), ); } } @@ -85,33 +84,18 @@ class SignupPage extends HookConsumerWidget { child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( - // TODO(MattsAttack): Find a better color for this (use `Theme.of(context).`). - color: const Color.fromARGB( - 255, - 34, - 29, - 43, - ), + color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(15), ), child: Form( key: formKey, child: Column( children: [ - const DecoratedBox( - // TODO(MattsAttack): Redesign this, it was for testing. - decoration: BoxDecoration( - // color: Colors.white, - // border: Border.all(color: Colors.white), - // borderRadius: BorderRadius.circular(5), - ), - child: Text( - 'Welcome to Nexus!', - style: TextStyle( - fontSize: 28, - // TODO(MattsAttack): Use `Theme.of(context).`. - color: Color.fromARGB(255, 221, 168, 230), - ), + Text( + 'Welcome to Nexus!', + style: TextStyle( + fontSize: 28, + color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), const SizedBox(height: 32), diff --git a/packages/app/lib/src/features/settings/application/settings_service.dart b/packages/app/lib/src/features/settings/application/settings_service.dart index 7969e45e..1734e418 100644 --- a/packages/app/lib/src/features/settings/application/settings_service.dart +++ b/packages/app/lib/src/features/settings/application/settings_service.dart @@ -1,8 +1,6 @@ /// This file provides a service to manage local user settings. library; -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -31,9 +29,7 @@ class SettingsService extends _$SettingsService { state = state.copyWith(themeMode: mode); // Persist the changes to a local database or the internet using the PreferencesRepository. - await ref - .read(preferencesRepositoryProvider) - .setString('prefs', json.encode(state)); + await ref.read(preferencesRepositoryProvider).update(state); } } diff --git a/packages/app/lib/src/features/settings/data/preferences_repository.dart b/packages/app/lib/src/features/settings/data/preferences_repository.dart index d4f19a7e..2d95f3c0 100644 --- a/packages/app/lib/src/features/settings/data/preferences_repository.dart +++ b/packages/app/lib/src/features/settings/data/preferences_repository.dart @@ -1,12 +1,12 @@ /// This library provides the ability to fetch and persist the user's settings. library; -import 'dart:convert'; - +import 'package:appwrite/appwrite.dart'; import 'package:flutter/material.dart'; +import 'package:json_annotation/json_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import '../../../utils/api.dart'; import '../domain/settings_model.dart'; part 'preferences_repository.g.dart'; @@ -17,46 +17,46 @@ abstract interface class PreferencesRepository { Future load(); /// Persist the user's settings to a local database or the internet. - Future setString(String key, String value); + Future update(SettingsModel newPrefs); } -final class _SharedPreferencesRepository implements PreferencesRepository { - _SharedPreferencesRepository(this.prefs); +final class _AppwritePreferencesRepository implements PreferencesRepository { + _AppwritePreferencesRepository(this.account); - final SharedPreferencesWithCache prefs; + final Account account; @override - Future load() => loadSettings(prefs); + Future load() => loadSettings(account); @override - Future setString(String key, String value) async => - await prefs.setString(key, value); + Future update(SettingsModel newPrefs) async => + await account.updatePrefs(prefs: newPrefs.toJson()); } /// Get the user's preferences. @Riverpod(keepAlive: true) PreferencesRepository preferencesRepository(PreferencesRepositoryRef ref) { - final prefs = ref.watch(sharedPreferencesProvider); + final account = ref.watch(accountsProvider); - return _SharedPreferencesRepository(prefs); + return _AppwritePreferencesRepository(account); } -/// Load a from a local database. +/// Load preferences from Appwrite. +/// +/// Returns [defaultSettings] if anything goes wrong. Future loadSettings( - SharedPreferencesWithCache prefs, + Account account, ) async { - final data = prefs.getString('prefs'); - final Object? decoded = data == null ? null : json.decode(data); - - return decoded is Map - ? SettingsModel.fromJson(decoded) - : defaultSettings; -} - -/// Get a [SharedPreferencesWithCache] instance. -@Riverpod(keepAlive: true) -SharedPreferencesWithCache sharedPreferences(SharedPreferencesRef ref) { - throw UnimplementedError(); + try { + final data = await account.getPrefs(); + + return SettingsModel.fromJson(data.data); + } on AppwriteException { + // TODO: Also handle invalid JSON. + return defaultSettings; + } on CheckedFromJsonException { + return defaultSettings; + } } /// The default settings, in case the user has none or they are corrupted. diff --git a/packages/app/lib/src/utils/api.dart b/packages/app/lib/src/utils/api.dart index 932fdcf3..0bdbb602 100644 --- a/packages/app/lib/src/utils/api.dart +++ b/packages/app/lib/src/utils/api.dart @@ -11,6 +11,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import '../../env/env.dart'; +import '../app/bootstrap.dart'; part 'api.freezed.dart'; part 'api.g.dart'; @@ -49,6 +50,17 @@ final ApiRepository apiInfo = Api( /// Get the Appwrite client. @Riverpod(keepAlive: true) Client client(ClientRef ref) { + throw UnimplementedError(); +} + +/// Create an Appwrite API [Client]. +/// +/// > [!WARNING] +/// > +/// > Do not use this! +/// > This is used to get around a lack of Riverpod while [Bootstrap]ping. +/// > Use [clientProvider] instead. +Client createClient() { return Client() .setEndpoint(apiInfo.url) .setProject(apiInfo.projectId) diff --git a/packages/app/lib/src/utils/toast.dart b/packages/app/lib/src/utils/toast.dart new file mode 100644 index 00000000..2b783050 --- /dev/null +++ b/packages/app/lib/src/utils/toast.dart @@ -0,0 +1,25 @@ +/// This library contains utilities for using snackbars. +library; + +import 'package:flutter/material.dart'; + +/// Extension method for [BuildContext], for showing uniform snackbars. +extension ShowSnackBar on BuildContext { + /// Show a [SnackBar] with the given [content]. + /// + /// ```dart + /// context.showSnackBar( + /// content: Text("Hello, world!"), + /// ); + /// ``` + ScaffoldFeatureController showSnackBar({ + required Widget content, + }) { + return ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: content, + behavior: SnackBarBehavior.floating, + ), + ); + } +} diff --git a/packages/app/macos/Podfile.lock b/packages/app/macos/Podfile.lock index c0dffc9b..e3e8a4c3 100644 --- a/packages/app/macos/Podfile.lock +++ b/packages/app/macos/Podfile.lock @@ -58,7 +58,7 @@ SPEC CHECKSUMS: package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 + url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404 window_to_front: 4cdc24ddd8461ad1a55fa06286d6a79d8b29e8d8 PODFILE CHECKSUM: c2e95c8c0fe03c5c57e438583cae4cc732296009 diff --git a/packages/app/test/helpers/mocks.dart b/packages/app/test/helpers/mocks.dart index c38a9338..349111af 100644 --- a/packages/app/test/helpers/mocks.dart +++ b/packages/app/test/helpers/mocks.dart @@ -1,10 +1,34 @@ +// Mocktail requires discarding futures. +// ignore_for_file: discarded_futures + +import 'package:appwrite/appwrite.dart'; import 'package:appwrite/models.dart'; +import 'package:appwrite/src/enums.dart'; import 'package:mocktail/mocktail.dart'; import 'package:nexus/src/features/auth/data/auth_repository.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -class MockSharedPreferences extends Mock - implements SharedPreferencesWithCache {} +void registerFallbacks() { + registerFallbackValue(HttpMethod.get); +} + +class MockClient extends Mock implements Client {} + +extension MockClientX on MockClient { + void mockCall({ + required String path, + required Response response, + }) { + when( + () => call( + any(), + path: path, + headers: any(named: 'headers'), + params: any(named: 'params'), + responseType: any(named: 'responseType'), + ), + ).thenAnswer((_) => Future.value(response)); + } +} class MockAuthRepository extends Mock implements AuthRepository {} diff --git a/packages/app/test/helpers/riverpod.dart b/packages/app/test/helpers/riverpod.dart index d17d86ef..080dd44c 100644 --- a/packages/app/test/helpers/riverpod.dart +++ b/packages/app/test/helpers/riverpod.dart @@ -10,13 +10,11 @@ typedef Overrides = List; /// A testing utility which creates a [ProviderContainer] and automatically /// disposes it at the end of the test. ProviderContainer createContainer({ - ProviderContainer? parent, Overrides overrides = const [], List? observers, }) { // Create a ProviderContainer, and optionally allow specifying parameters. final container = ProviderContainer( - parent: parent, overrides: overrides, observers: observers, ); diff --git a/packages/app/test/src/app/app_test.dart b/packages/app/test/src/app/app_test.dart index 51ba6ec5..a1cfb560 100644 --- a/packages/app/test/src/app/app_test.dart +++ b/packages/app/test/src/app/app_test.dart @@ -14,7 +14,6 @@ import '../../helpers/mocks.dart'; extension _WidgetTesterX on WidgetTester { Future pumpWidgetPage() async { - final mockSharedPreferences = MockSharedPreferences(); final mockAuthRepository = MockAuthRepository(); when(mockAuthRepository.checkUserAuth).thenAnswer((_) => Future.value()); @@ -22,7 +21,6 @@ extension _WidgetTesterX on WidgetTester { ProviderScope( overrides: [ authRepositoryProvider.overrideWithValue(mockAuthRepository), - sharedPreferencesProvider.overrideWithValue(mockSharedPreferences), initialSettingsProvider.overrideWithValue(defaultSettings), ], child: const App(), diff --git a/packages/app/test/src/app/bootstrap_test.dart b/packages/app/test/src/app/bootstrap_test.dart index 78f05a2b..fb3848af 100644 --- a/packages/app/test/src/app/bootstrap_test.dart +++ b/packages/app/test/src/app/bootstrap_test.dart @@ -1,29 +1,24 @@ +import 'package:appwrite/appwrite.dart'; import 'package:checks/checks.dart'; -import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:nexus/src/app/app.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import '../../helpers/mocks.dart'; -Future getSharedPreferences({ - required SharedPreferencesWithCacheOptions cacheOptions, - Map? cache, -}) async { - return MockSharedPreferences(); -} - void main() { - setUpAll(() { - registerFallbackValue(Container()); - }); + setUpAll(registerFallbacks); test('main does not throw', () async { const app = App(); + final env = ( runApp: (_) {}, - getSharedPreferences: getSharedPreferences, + createClient: () => MockClient() + ..mockCall( + path: any(named: '/account/prefs'), + response: Response(data: {}), + ) ); await check(app.bootstrap(env)).completes(); diff --git a/packages/app/test/src/features/settings/application/settings_service_test.dart b/packages/app/test/src/features/settings/application/settings_service_test.dart index 0bf24027..4df4e95d 100644 --- a/packages/app/test/src/features/settings/application/settings_service_test.dart +++ b/packages/app/test/src/features/settings/application/settings_service_test.dart @@ -1,24 +1,17 @@ import 'package:checks/checks.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:nexus/src/features/settings/application/settings_service.dart'; import 'package:nexus/src/features/settings/data/preferences_repository.dart'; import 'package:nexus/src/features/settings/domain/settings_model.dart'; -import '../../../../helpers/mocks.dart'; import '../../../../helpers/riverpod.dart'; void main() { group('SettingsService', () { test('should update the theme mode', () async { - final mockSharedPreferences = MockSharedPreferences(); - when(() => mockSharedPreferences.setString(any(), any())) - .thenAnswer((_) async => true); - final container = createContainer( overrides: [ - sharedPreferencesProvider.overrideWithValue(mockSharedPreferences), initialSettingsProvider.overrideWithValue(defaultSettings), ], ); diff --git a/packages/app/test/src/features/settings/data/preferences_repository_test.dart b/packages/app/test/src/features/settings/data/preferences_repository_test.dart index 785a13fa..819caef4 100644 --- a/packages/app/test/src/features/settings/data/preferences_repository_test.dart +++ b/packages/app/test/src/features/settings/data/preferences_repository_test.dart @@ -1,22 +1,30 @@ +import 'package:appwrite/appwrite.dart'; import 'package:checks/checks.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; import 'package:nexus/src/features/settings/application/settings_service.dart'; import 'package:nexus/src/features/settings/data/preferences_repository.dart'; +import 'package:nexus/src/utils/api.dart' show clientProvider; import '../../../../helpers/mocks.dart'; import '../../../../helpers/riverpod.dart'; void main() { + setUpAll(registerFallbacks); + group('PreferencesRepository', () { test('should update the theme mode', () async { // Arrange - final mockSharedPreferences = MockSharedPreferences(); + final client = MockClient() + ..mockCall( + path: any(named: '/account/prefs'), + response: Response(data: {}), + ); final container = createContainer( overrides: [ - sharedPreferencesProvider.overrideWithValue(mockSharedPreferences), + clientProvider.overrideWithValue(client), initialSettingsProvider.overrideWithValue(defaultSettings), ], ); @@ -31,14 +39,15 @@ void main() { test('should decode the theme mode', () async { // Arrange - final mockSharedPreferences = MockSharedPreferences(); - - when(() => mockSharedPreferences.getString('prefs')) - .thenReturn('{"themeMode":"dark"}'); + final client = MockClient() + ..mockCall( + path: any(named: '/account/prefs'), + response: Response(data: {}), + ); final container = createContainer( overrides: [ - sharedPreferencesProvider.overrideWithValue(mockSharedPreferences), + clientProvider.overrideWithValue(client), initialSettingsProvider.overrideWithValue(defaultSettings), ], ); @@ -53,7 +62,7 @@ void main() { }); group('sharedPreferences', () { - test('should throw an error if SharedPreferences is not provided', () { + test('should throw an error if Account is not provided', () { // Arrange final container = createContainer(); diff --git a/packages/app/test/src/features/settings/presentation/preferences/settings_page_test.dart b/packages/app/test/src/features/settings/presentation/preferences/settings_page_test.dart index dcd673f7..aa9c1640 100644 --- a/packages/app/test/src/features/settings/presentation/preferences/settings_page_test.dart +++ b/packages/app/test/src/features/settings/presentation/preferences/settings_page_test.dart @@ -1,40 +1,21 @@ -import 'dart:convert'; - import 'package:checks/checks.dart'; import 'package:flutter/material.dart'; import 'package:flutter_checks/flutter_checks.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; import 'package:nexus/src/features/settings/application/settings_service.dart'; import 'package:nexus/src/features/settings/data/preferences_repository.dart'; -import 'package:nexus/src/features/settings/domain/settings_model.dart'; import 'package:nexus/src/features/settings/presentation/preferences/settings_page.dart'; import '../../../../../helpers/accessibility.dart'; -import '../../../../../helpers/mocks.dart'; import '../../../../../helpers/pump_app.dart'; void main() { group('SettingsPage', () { testWidgets('changing the dropdown value should save the theme', (tester) async { - final mockSharedPreferences = MockSharedPreferences(); - - final darkMode = json.encode( - const SettingsModel( - themeMode: ThemeMode.dark, - ).toJson(), - ); - - when(() => mockSharedPreferences.setString('prefs', darkMode)) - .thenAnswer((_) async {}); - - when(() => mockSharedPreferences.getString('prefs')).thenReturn(darkMode); - await tester.pumpApp( const Material(child: SettingsPage()), overrides: [ - sharedPreferencesProvider.overrideWithValue(mockSharedPreferences), initialSettingsProvider.overrideWithValue(defaultSettings), ], ); @@ -46,15 +27,11 @@ void main() { await tester.pumpAndSettle(); check(find.text('Dark Theme')).findsOne(); - - verify(() => mockSharedPreferences.setString('prefs', darkMode)) - .called(1); }); testAccessibilityGuidelines( const Material(child: SettingsPage()), overrides: [ - sharedPreferencesProvider.overrideWithValue(MockSharedPreferences()), initialSettingsProvider.overrideWithValue(defaultSettings), ], );