From edff1f29db2d543489bec48c228bfa8f61c733b8 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:56:38 +0200 Subject: [PATCH] test: scaffolding + first provider test (hidden_libraries) --- .../base_shared_preferences_service.dart | 9 ++ pubspec.lock | 2 +- pubspec.yaml | 1 + .../hidden_libraries_provider_test.dart | 109 ++++++++++++++++++ test/test_helpers/prefs.dart | 16 +++ 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 test/providers/hidden_libraries_provider_test.dart create mode 100644 test/test_helpers/prefs.dart diff --git a/lib/services/base_shared_preferences_service.dart b/lib/services/base_shared_preferences_service.dart index 61fd44cd..ff7786f4 100644 --- a/lib/services/base_shared_preferences_service.dart +++ b/lib/services/base_shared_preferences_service.dart @@ -56,6 +56,15 @@ abstract class BaseSharedPreferencesService { }(); } + /// Drop all cached singleton instances and the shared cache future so the + /// next `getInstance()` call rebuilds against the current + /// `SharedPreferences.setMockInitialValues(...)`. Test-only. + @visibleForTesting + static void resetForTesting() { + _instances.clear(); + _cacheFuture = null; + } + /// Typed read helpers — return the stored value or [defaultValue] when missing. bool readBool(String key, {bool defaultValue = false}) => _cache.getBool(key) ?? defaultValue; int readInt(String key, {int defaultValue = 0}) => _cache.getInt(key) ?? defaultValue; diff --git a/pubspec.lock b/pubspec.lock index e9ca8079..0ce9fa69 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1063,7 +1063,7 @@ packages: source: hosted version: "2.4.1" shared_preferences_platform_interface: - dependency: transitive + dependency: "direct dev" description: name: shared_preferences_platform_interface sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" diff --git a/pubspec.yaml b/pubspec.yaml index b3969126..eff9cd59 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -79,6 +79,7 @@ dev_dependencies: slang_build_runner: ^4.14.0 dart_code_linter: ^3.2.1 drift_dev: ^2.28.3 + shared_preferences_platform_interface: ^2.4.0 dependency_overrides: auto_updater_platform_interface: diff --git a/test/providers/hidden_libraries_provider_test.dart b/test/providers/hidden_libraries_provider_test.dart new file mode 100644 index 00000000..fecab30b --- /dev/null +++ b/test/providers/hidden_libraries_provider_test.dart @@ -0,0 +1,109 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/providers/hidden_libraries_provider.dart'; +import 'package:plezy/services/base_shared_preferences_service.dart'; +import 'package:plezy/services/storage_service.dart'; + +import '../test_helpers/prefs.dart'; + +void main() { + setUp(resetSharedPreferencesForTest); + + group('HiddenLibrariesProvider', () { + test('starts uninitialized and exposes empty set', () async { + final p = HiddenLibrariesProvider(); + expect(p.isInitialized, isFalse); + expect(p.hiddenLibraryKeys, isEmpty); + await p.ensureInitialized(); + expect(p.isInitialized, isTrue); + expect(p.hiddenLibraryKeys, isEmpty); + p.dispose(); + }); + + test('hideLibrary persists key and notifies listeners', () async { + final p = HiddenLibrariesProvider(); + await p.ensureInitialized(); + + var notified = 0; + p.addListener(() => notified++); + + await p.hideLibrary('lib-1'); + expect(p.isLibraryHidden('lib-1'), isTrue); + expect(p.hiddenLibraryKeys, contains('lib-1')); + expect(notified, 1); + + // Same key again → no-op, no extra notification + await p.hideLibrary('lib-1'); + expect(notified, 1); + + p.dispose(); + }); + + test('persists across provider instances via SharedPreferences', () async { + final first = HiddenLibrariesProvider(); + await first.ensureInitialized(); + await first.hideLibrary('lib-A'); + await first.hideLibrary('lib-B'); + first.dispose(); + + // Reset only the cached singleton, NOT SharedPreferences — values survive. + BaseSharedPreferencesService.resetForTesting(); + + final second = HiddenLibrariesProvider(); + await second.ensureInitialized(); + expect(second.isLibraryHidden('lib-A'), isTrue); + expect(second.isLibraryHidden('lib-B'), isTrue); + expect(second.isLibraryHidden('lib-C'), isFalse); + second.dispose(); + }); + + test('unhideLibrary removes the key', () async { + final p = HiddenLibrariesProvider(); + await p.ensureInitialized(); + await p.hideLibrary('lib-1'); + await p.hideLibrary('lib-2'); + + await p.unhideLibrary('lib-1'); + expect(p.isLibraryHidden('lib-1'), isFalse); + expect(p.isLibraryHidden('lib-2'), isTrue); + + // Unhiding an already-absent key is a no-op + await p.unhideLibrary('lib-3'); + expect(p.hiddenLibraryKeys, equals({'lib-2'})); + + p.dispose(); + }); + + test('refresh re-reads from storage', () async { + final p = HiddenLibrariesProvider(); + await p.ensureInitialized(); + expect(p.hiddenLibraryKeys, isEmpty); + + // Mutate underlying storage out-of-band, then refresh. + final storage = await StorageService.getInstance(); + await storage.saveHiddenLibraries({'external-1', 'external-2'}); + + await p.refresh(); + expect(p.hiddenLibraryKeys, equals({'external-1', 'external-2'})); + + p.dispose(); + }); + + test('hiddenLibraryKeys returns an unmodifiable view', () async { + final p = HiddenLibrariesProvider(); + await p.ensureInitialized(); + await p.hideLibrary('lib-1'); + + expect(() => p.hiddenLibraryKeys.add('mutated'), throwsUnsupportedError); + + p.dispose(); + }); + + test('safeNotifyListeners no-ops after dispose', () async { + final p = HiddenLibrariesProvider(); + await p.ensureInitialized(); + p.dispose(); + // Should not throw, even though notifyListeners after dispose normally does. + await p.refresh(); + }); + }); +} diff --git a/test/test_helpers/prefs.dart b/test/test_helpers/prefs.dart new file mode 100644 index 00000000..2e43d461 --- /dev/null +++ b/test/test_helpers/prefs.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/services/base_shared_preferences_service.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart'; +import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart'; + +/// Reset shared-prefs platform mocks AND the cached singleton instances. +/// Call from `setUp` so each test starts with a clean slate. +void resetSharedPreferencesForTest({Map initialAsync = const {}}) { + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues({}); + SharedPreferencesAsyncPlatform.instance = initialAsync.isEmpty + ? InMemorySharedPreferencesAsync.empty() + : InMemorySharedPreferencesAsync.withData(initialAsync); + BaseSharedPreferencesService.resetForTesting(); +}