@@ -113,7 +113,8 @@ class _ProfileSessionScreenState extends State<ProfileSessionScreen> {
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => HiddenLibrariesProvider(storageService: context.read<StorageService>()),
|
||||
create: (context) =>
|
||||
HiddenLibrariesProvider(storageService: context.read<StorageService>(), profileId: activeId),
|
||||
lazy: true,
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
|
||||
@@ -7,11 +7,12 @@ import '../services/storage_service.dart';
|
||||
/// all other screens are automatically updated.
|
||||
class HiddenLibrariesProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
StorageService? _storageService;
|
||||
final String? profileId;
|
||||
Set<String> _hiddenLibraryKeys = {};
|
||||
bool _isInitialized = false;
|
||||
Future<void>? _initFuture;
|
||||
|
||||
HiddenLibrariesProvider({StorageService? storageService}) : _storageService = storageService {
|
||||
HiddenLibrariesProvider({StorageService? storageService, this.profileId}) : _storageService = storageService {
|
||||
// Start initialization eagerly to reduce race conditions
|
||||
_initFuture = _initialize();
|
||||
}
|
||||
@@ -29,19 +30,36 @@ class HiddenLibrariesProvider extends ChangeNotifier with DisposableChangeNotifi
|
||||
/// Initialize the provider by loading hidden libraries from storage
|
||||
Future<void> _initialize() async {
|
||||
if (_isInitialized) return;
|
||||
final storage = _storageService ??= await StorageService.getInstance();
|
||||
_hiddenLibraryKeys = storage.getHiddenLibraries();
|
||||
await _loadFromStorage();
|
||||
_isInitialized = true;
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _loadFromStorage() async {
|
||||
final storage = _storageService ??= await StorageService.getInstance();
|
||||
final scopedProfileId = profileId;
|
||||
_hiddenLibraryKeys = scopedProfileId == null
|
||||
? storage.getHiddenLibraries()
|
||||
: storage.getHiddenLibrariesForProfile(scopedProfileId);
|
||||
}
|
||||
|
||||
Future<void> _saveToStorage() async {
|
||||
final storage = _storageService ??= await StorageService.getInstance();
|
||||
final scopedProfileId = profileId;
|
||||
if (scopedProfileId == null) {
|
||||
await storage.saveHiddenLibraries(_hiddenLibraryKeys);
|
||||
} else {
|
||||
await storage.saveHiddenLibrariesForProfile(scopedProfileId, _hiddenLibraryKeys);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hide a library by its key
|
||||
/// Updates both in-memory state and persistent storage
|
||||
Future<void> hideLibrary(String libraryKey) async {
|
||||
if (!_isInitialized) await _initialize();
|
||||
if (!_hiddenLibraryKeys.contains(libraryKey)) {
|
||||
_hiddenLibraryKeys = Set.from(_hiddenLibraryKeys)..add(libraryKey);
|
||||
await _storageService!.saveHiddenLibraries(_hiddenLibraryKeys);
|
||||
await _saveToStorage();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -52,7 +70,7 @@ class HiddenLibrariesProvider extends ChangeNotifier with DisposableChangeNotifi
|
||||
if (!_isInitialized) await _initialize();
|
||||
if (_hiddenLibraryKeys.contains(libraryKey)) {
|
||||
_hiddenLibraryKeys = Set.from(_hiddenLibraryKeys)..remove(libraryKey);
|
||||
await _storageService!.saveHiddenLibraries(_hiddenLibraryKeys);
|
||||
await _saveToStorage();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -63,8 +81,8 @@ class HiddenLibrariesProvider extends ChangeNotifier with DisposableChangeNotifi
|
||||
/// Refresh hidden libraries from storage
|
||||
/// Useful if storage was modified outside the provider
|
||||
Future<void> refresh() async {
|
||||
final storage = _storageService ??= await StorageService.getInstance();
|
||||
_hiddenLibraryKeys = storage.getHiddenLibraries();
|
||||
await _loadFromStorage();
|
||||
_isInitialized = true;
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +118,30 @@ bool shouldPassTvosMenuToSystem({
|
||||
isCurrentTabRoot;
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
enum ProfileInvalidationAction { none, waitForProfileSwitch, invalidateNow }
|
||||
|
||||
@visibleForTesting
|
||||
ProfileInvalidationAction profileInvalidationAction({
|
||||
required String? previousProfileId,
|
||||
required String? currentProfileId,
|
||||
required bool wasBindingPreviously,
|
||||
required bool isBindingNow,
|
||||
required bool hasPendingProfileSwitchInvalidation,
|
||||
required String? pendingProfileSwitchInvalidationId,
|
||||
}) {
|
||||
if (currentProfileId != previousProfileId) {
|
||||
return ProfileInvalidationAction.waitForProfileSwitch;
|
||||
}
|
||||
if (hasPendingProfileSwitchInvalidation && pendingProfileSwitchInvalidationId == currentProfileId) {
|
||||
return ProfileInvalidationAction.none;
|
||||
}
|
||||
if (wasBindingPreviously && !isBindingNow) {
|
||||
return ProfileInvalidationAction.invalidateNow;
|
||||
}
|
||||
return ProfileInvalidationAction.none;
|
||||
}
|
||||
|
||||
class MainScreen extends StatefulWidget {
|
||||
final bool isOfflineMode;
|
||||
|
||||
@@ -204,6 +228,8 @@ class _MainScreenState extends State<MainScreen>
|
||||
// we only invalidate on id change and the libraries sidebar keeps
|
||||
// stale entries until the user switches profiles.
|
||||
bool _wasBindingPrev = false;
|
||||
bool _hasPendingProfileSwitchInvalidation = false;
|
||||
String? _pendingProfileSwitchInvalidationId;
|
||||
|
||||
/// Subscription to MultiServerManager status changes. Used to resume any
|
||||
/// queued downloads as soon as a Plex client comes online for the first
|
||||
@@ -463,10 +489,20 @@ class _MainScreenState extends State<MainScreen>
|
||||
if (activeProfile == null) return;
|
||||
final id = activeProfile.activeId;
|
||||
final isBindingNow = activeProfile.isBinding;
|
||||
final action = profileInvalidationAction(
|
||||
previousProfileId: _lastSeenProfileId,
|
||||
currentProfileId: id,
|
||||
wasBindingPreviously: _wasBindingPrev,
|
||||
isBindingNow: isBindingNow,
|
||||
hasPendingProfileSwitchInvalidation: _hasPendingProfileSwitchInvalidation,
|
||||
pendingProfileSwitchInvalidationId: _pendingProfileSwitchInvalidationId,
|
||||
);
|
||||
|
||||
if (id != _lastSeenProfileId) {
|
||||
if (action == ProfileInvalidationAction.waitForProfileSwitch) {
|
||||
_lastSeenProfileId = id;
|
||||
_wasBindingPrev = isBindingNow;
|
||||
_hasPendingProfileSwitchInvalidation = true;
|
||||
_pendingProfileSwitchInvalidationId = id;
|
||||
// We're called inside the synchronous notify cascade *before* the
|
||||
// binder's listener has fired (registration order). At this exact
|
||||
// instant `_isBinding` is still false, so calling awaitBindingSettle
|
||||
@@ -474,10 +510,22 @@ class _MainScreenState extends State<MainScreen>
|
||||
// listener gets to flip the flag first, then wait properly.
|
||||
unawaited(
|
||||
Future.microtask(() async {
|
||||
final scheduledProfileId = id;
|
||||
if (!mounted) return;
|
||||
await activeProfile.awaitBindingSettle();
|
||||
if (!mounted) return;
|
||||
await _invalidateAllScreens();
|
||||
try {
|
||||
if (_hasPendingProfileSwitchInvalidation &&
|
||||
_pendingProfileSwitchInvalidationId == scheduledProfileId &&
|
||||
activeProfile.activeId == scheduledProfileId) {
|
||||
await _invalidateAllScreens();
|
||||
}
|
||||
} finally {
|
||||
if (_hasPendingProfileSwitchInvalidation && _pendingProfileSwitchInvalidationId == scheduledProfileId) {
|
||||
_hasPendingProfileSwitchInvalidation = false;
|
||||
_pendingProfileSwitchInvalidationId = null;
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
return;
|
||||
@@ -487,7 +535,7 @@ class _MainScreenState extends State<MainScreen>
|
||||
// (true → false transition). Fires after borrow / connection-removal
|
||||
// flows trigger ActiveProfileBinder.rebindIfActive, so the libraries
|
||||
// sidebar reflects the new server set without an app restart.
|
||||
if (_wasBindingPrev && !isBindingNow) {
|
||||
if (action == ProfileInvalidationAction.invalidateNow) {
|
||||
_wasBindingPrev = isBindingNow;
|
||||
unawaited(_invalidateAllScreens());
|
||||
return;
|
||||
@@ -1274,6 +1322,9 @@ class _MainScreenState extends State<MainScreen>
|
||||
appLogger.w('Failed to clear ApiCache on profile switch', error: e, stackTrace: st);
|
||||
}
|
||||
|
||||
await hiddenLibrariesProvider.refresh();
|
||||
if (!mounted) return;
|
||||
|
||||
librariesProvider.clear();
|
||||
|
||||
if (multiServerProvider.serverManager.serverIds.isNotEmpty) {
|
||||
@@ -1286,7 +1337,6 @@ class _MainScreenState extends State<MainScreen>
|
||||
await librariesProvider.refresh();
|
||||
}
|
||||
|
||||
unawaited(hiddenLibrariesProvider.refresh());
|
||||
playbackStateProvider.clearShuffle();
|
||||
|
||||
if (_discoverKey.currentState case final FullRefreshable refreshable) {
|
||||
|
||||
@@ -224,8 +224,33 @@ class StorageService extends BaseSharedPreferencesService {
|
||||
await _setStringList('$_userPrefix$_keyHiddenLibraries', libraryKeys.toList());
|
||||
}
|
||||
|
||||
Future<void> saveHiddenLibrariesForProfile(String profileId, Set<String> libraryKeys) async {
|
||||
await _setStringList('${_userPrefixForProfileId(profileId)}$_keyHiddenLibraries', libraryKeys.toList());
|
||||
}
|
||||
|
||||
Set<String> getHiddenLibraries() {
|
||||
final jsonString = _getScopedString(_keyHiddenLibraries);
|
||||
return _decodeStringSet(jsonString);
|
||||
}
|
||||
|
||||
Set<String> getHiddenLibrariesForProfile(String profileId) {
|
||||
final scopedKey = '${_userPrefixForProfileId(profileId)}$_keyHiddenLibraries';
|
||||
var jsonString = prefs.getString(scopedKey);
|
||||
if (jsonString == null && getActiveProfileId() == profileId) {
|
||||
// One-time migration from the legacy unscoped key, but only for the
|
||||
// currently active profile. Otherwise merely opening another profile's
|
||||
// scoped provider could steal legacy preferences into the wrong scope.
|
||||
final legacy = prefs.getString(_keyHiddenLibraries);
|
||||
if (legacy != null) {
|
||||
prefs.setString(scopedKey, legacy);
|
||||
prefs.remove(_keyHiddenLibraries);
|
||||
jsonString = legacy;
|
||||
}
|
||||
}
|
||||
return _decodeStringSet(jsonString);
|
||||
}
|
||||
|
||||
Set<String> _decodeStringSet(String? jsonString) {
|
||||
if (jsonString == null) return {};
|
||||
|
||||
try {
|
||||
|
||||
@@ -12,6 +12,7 @@ import 'package:plezy/profiles/profile.dart';
|
||||
import 'package:plezy/profiles/profile_connection_registry.dart';
|
||||
import 'package:plezy/profiles/profile_registry.dart';
|
||||
import 'package:plezy/providers/discover_provider.dart';
|
||||
import 'package:plezy/providers/hidden_libraries_provider.dart';
|
||||
import 'package:plezy/providers/multi_server_provider.dart';
|
||||
import 'package:plezy/services/data_aggregation_service.dart';
|
||||
import 'package:plezy/services/multi_server_manager.dart';
|
||||
@@ -47,6 +48,7 @@ void main() {
|
||||
final serverManager = MultiServerManager();
|
||||
final multiServer = MultiServerProvider(serverManager, DataAggregationService(serverManager));
|
||||
final discoverProviders = <DiscoverProvider>[];
|
||||
final hiddenProviders = <HiddenLibrariesProvider>[];
|
||||
final disposedActiveIds = <String>[];
|
||||
|
||||
addTearDown(() async {
|
||||
@@ -64,6 +66,8 @@ void main() {
|
||||
final kids = Profile.local(id: 'local-kids', displayName: 'Kids', createdAt: DateTime(2026, 1, 2));
|
||||
await profileRegistry.upsert(owner);
|
||||
await profileRegistry.upsert(kids);
|
||||
await storage.saveHiddenLibrariesForProfile(owner.id, {'srv:owner'});
|
||||
await storage.saveHiddenLibrariesForProfile(kids.id, {'srv:kids'});
|
||||
await storage.setActiveProfileId(owner.id);
|
||||
await activeProfile.initialize();
|
||||
|
||||
@@ -77,8 +81,11 @@ void main() {
|
||||
child: MaterialApp(
|
||||
home: ProfileSessionScreen.forTesting(
|
||||
initialPromptHandled: true,
|
||||
profileShellBuilder: (context) =>
|
||||
_ProfileProbeShell(discoverProviders: discoverProviders, disposedActiveIds: disposedActiveIds),
|
||||
profileShellBuilder: (context) => _ProfileProbeShell(
|
||||
discoverProviders: discoverProviders,
|
||||
hiddenProviders: hiddenProviders,
|
||||
disposedActiveIds: disposedActiveIds,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -87,8 +94,13 @@ void main() {
|
||||
|
||||
expect(find.text('active:local-owner'), findsOneWidget);
|
||||
expect(discoverProviders, hasLength(1));
|
||||
expect(hiddenProviders, hasLength(1));
|
||||
final ownerNavigator = profileNavigationRegistry.navigator;
|
||||
final ownerDiscover = discoverProviders.single;
|
||||
final ownerHidden = hiddenProviders.single;
|
||||
await ownerHidden.ensureInitialized();
|
||||
expect(ownerHidden.profileId, owner.id);
|
||||
expect(ownerHidden.hiddenLibraryKeys, {'srv:owner'});
|
||||
|
||||
await tester.tap(find.byKey(const ValueKey('push-profile-route')));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -102,14 +114,24 @@ void main() {
|
||||
expect(disposedActiveIds, contains('local-owner'));
|
||||
expect(discoverProviders, hasLength(2));
|
||||
expect(discoverProviders.last, isNot(same(ownerDiscover)));
|
||||
expect(hiddenProviders, hasLength(2));
|
||||
expect(hiddenProviders.last, isNot(same(ownerHidden)));
|
||||
await hiddenProviders.last.ensureInitialized();
|
||||
expect(hiddenProviders.last.profileId, kids.id);
|
||||
expect(hiddenProviders.last.hiddenLibraryKeys, {'srv:kids'});
|
||||
expect(profileNavigationRegistry.navigator, isNot(same(ownerNavigator)));
|
||||
});
|
||||
}
|
||||
|
||||
class _ProfileProbeShell extends StatefulWidget {
|
||||
const _ProfileProbeShell({required this.discoverProviders, required this.disposedActiveIds});
|
||||
const _ProfileProbeShell({
|
||||
required this.discoverProviders,
|
||||
required this.hiddenProviders,
|
||||
required this.disposedActiveIds,
|
||||
});
|
||||
|
||||
final List<DiscoverProvider> discoverProviders;
|
||||
final List<HiddenLibrariesProvider> hiddenProviders;
|
||||
final List<String> disposedActiveIds;
|
||||
|
||||
@override
|
||||
@@ -118,16 +140,21 @@ class _ProfileProbeShell extends StatefulWidget {
|
||||
|
||||
class _ProfileProbeShellState extends State<_ProfileProbeShell> {
|
||||
DiscoverProvider? _discoverProvider;
|
||||
HiddenLibrariesProvider? _hiddenProvider;
|
||||
String _activeId = 'none';
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_discoverProvider = context.read<DiscoverProvider>();
|
||||
_hiddenProvider = context.read<HiddenLibrariesProvider>();
|
||||
_activeId = context.read<ActiveProfileProvider>().activeId ?? 'none';
|
||||
if (widget.discoverProviders.isEmpty || !identical(widget.discoverProviders.last, _discoverProvider)) {
|
||||
widget.discoverProviders.add(_discoverProvider!);
|
||||
}
|
||||
if (widget.hiddenProviders.isEmpty || !identical(widget.hiddenProviders.last, _hiddenProvider)) {
|
||||
widget.hiddenProviders.add(_hiddenProvider!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -88,6 +88,47 @@ void main() {
|
||||
p.dispose();
|
||||
});
|
||||
|
||||
test('explicit profile id stays isolated when active profile changes', () async {
|
||||
final storage = await StorageService.getInstance();
|
||||
await storage.saveHiddenLibrariesForProfile('owner', {'srv:movies'});
|
||||
await storage.saveHiddenLibrariesForProfile('kids', {'srv:kids'});
|
||||
await storage.setActiveProfileId('owner');
|
||||
|
||||
final ownerProvider = HiddenLibrariesProvider(storageService: storage, profileId: 'owner');
|
||||
final kidsProvider = HiddenLibrariesProvider(storageService: storage, profileId: 'kids');
|
||||
|
||||
await storage.setActiveProfileId('kids');
|
||||
await ownerProvider.refresh();
|
||||
await kidsProvider.refresh();
|
||||
|
||||
expect(ownerProvider.hiddenLibraryKeys, {'srv:movies'});
|
||||
expect(kidsProvider.hiddenLibraryKeys, {'srv:kids'});
|
||||
|
||||
await ownerProvider.hideLibrary('srv:documentaries');
|
||||
|
||||
expect(storage.getHiddenLibrariesForProfile('owner'), {'srv:movies', 'srv:documentaries'});
|
||||
expect(storage.getHiddenLibrariesForProfile('kids'), {'srv:kids'});
|
||||
expect(storage.getHiddenLibraries(), {'srv:kids'});
|
||||
|
||||
ownerProvider.dispose();
|
||||
kidsProvider.dispose();
|
||||
});
|
||||
|
||||
test('refresh marks provider initialized after deterministic reload', () async {
|
||||
final storage = await StorageService.getInstance();
|
||||
await storage.saveHiddenLibrariesForProfile('owner', {'srv:movies'});
|
||||
|
||||
final p = HiddenLibrariesProvider(storageService: storage, profileId: 'owner');
|
||||
expect(p.isInitialized, isFalse);
|
||||
|
||||
await p.refresh();
|
||||
|
||||
expect(p.isInitialized, isTrue);
|
||||
expect(p.hiddenLibraryKeys, {'srv:movies'});
|
||||
|
||||
p.dispose();
|
||||
});
|
||||
|
||||
test('hiddenLibraryKeys returns an unmodifiable view', () async {
|
||||
final p = HiddenLibrariesProvider();
|
||||
await p.ensureInitialized();
|
||||
|
||||
@@ -70,6 +70,58 @@ void main() {
|
||||
expect(shouldPass(isAppleTV: false), isFalse);
|
||||
});
|
||||
|
||||
test('profile switch waits for one post-bind invalidation', () {
|
||||
expect(
|
||||
profileInvalidationAction(
|
||||
previousProfileId: 'owner',
|
||||
currentProfileId: 'kids',
|
||||
wasBindingPreviously: false,
|
||||
isBindingNow: false,
|
||||
hasPendingProfileSwitchInvalidation: false,
|
||||
pendingProfileSwitchInvalidationId: null,
|
||||
),
|
||||
ProfileInvalidationAction.waitForProfileSwitch,
|
||||
);
|
||||
|
||||
expect(
|
||||
profileInvalidationAction(
|
||||
previousProfileId: 'kids',
|
||||
currentProfileId: 'kids',
|
||||
wasBindingPreviously: true,
|
||||
isBindingNow: false,
|
||||
hasPendingProfileSwitchInvalidation: true,
|
||||
pendingProfileSwitchInvalidationId: 'kids',
|
||||
),
|
||||
ProfileInvalidationAction.none,
|
||||
);
|
||||
});
|
||||
|
||||
test('same-profile rebind invalidates once when binding settles', () {
|
||||
expect(
|
||||
profileInvalidationAction(
|
||||
previousProfileId: 'owner',
|
||||
currentProfileId: 'owner',
|
||||
wasBindingPreviously: true,
|
||||
isBindingNow: false,
|
||||
hasPendingProfileSwitchInvalidation: false,
|
||||
pendingProfileSwitchInvalidationId: null,
|
||||
),
|
||||
ProfileInvalidationAction.invalidateNow,
|
||||
);
|
||||
|
||||
expect(
|
||||
profileInvalidationAction(
|
||||
previousProfileId: 'owner',
|
||||
currentProfileId: 'owner',
|
||||
wasBindingPreviously: false,
|
||||
isBindingNow: false,
|
||||
hasPendingProfileSwitchInvalidation: false,
|
||||
pendingProfileSwitchInvalidationId: null,
|
||||
),
|
||||
ProfileInvalidationAction.none,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('side navigation bleed animates from the previous value', (tester) async {
|
||||
Widget build(double targetBleed) {
|
||||
return Directionality(
|
||||
|
||||
@@ -171,6 +171,61 @@ void main() {
|
||||
expect(captured.single.queryParameters['count'], '21');
|
||||
});
|
||||
|
||||
test('getOnDeckFromAllServers filters hidden Plex continue-watching libraries', () async {
|
||||
final client = PlexClient.forTesting(
|
||||
config: PlexConfig(
|
||||
baseUrl: 'https://plex.example.com',
|
||||
token: 'token',
|
||||
clientIdentifier: 'client-id',
|
||||
product: 'Plezy',
|
||||
version: 'test',
|
||||
),
|
||||
serverId: ServerId('plex-1'),
|
||||
serverName: 'Plex',
|
||||
httpClient: MockClient((req) async {
|
||||
if (req.url.path == '/hubs') {
|
||||
return _json({
|
||||
'MediaContainer': {
|
||||
'Hub': [
|
||||
{
|
||||
'key': '/hubs/home/continueWatching',
|
||||
'title': 'Continue Watching',
|
||||
'type': 'mixed',
|
||||
'hubIdentifier': 'home.continue',
|
||||
'size': 2,
|
||||
'Metadata': [
|
||||
{
|
||||
'ratingKey': 'movie-visible',
|
||||
'type': 'movie',
|
||||
'title': 'Visible Movie',
|
||||
'lastViewedAt': 100,
|
||||
'librarySectionID': 1,
|
||||
},
|
||||
{
|
||||
'ratingKey': 'movie-hidden',
|
||||
'type': 'movie',
|
||||
'title': 'Hidden Movie',
|
||||
'lastViewedAt': 200,
|
||||
'librarySectionID': 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
return http.Response('unexpected request', 500);
|
||||
}),
|
||||
);
|
||||
addTearDown(client.close);
|
||||
manager.debugRegisterClientForTesting(client);
|
||||
|
||||
final result = await service.getOnDeckFromAllServers(limit: 10, hiddenLibraryKeys: {'plex-1:2'});
|
||||
|
||||
expect(result.items.map((item) => item.id), ['movie-visible']);
|
||||
expect(result.succeededServerIds, {'plex-1'});
|
||||
});
|
||||
|
||||
test('getOnDeckFromAllServers hides duplicate show entries by stable show ids', () async {
|
||||
final client = PlexClient.forTesting(
|
||||
config: PlexConfig(
|
||||
|
||||
@@ -153,6 +153,19 @@ void main() {
|
||||
expect(s.getHiddenLibraries(), equals({'lib-a', 'lib-b'}));
|
||||
});
|
||||
|
||||
test('explicit profile helpers isolate hidden libraries from active profile changes', () async {
|
||||
final s = await StorageService.getInstance();
|
||||
|
||||
await s.setActiveProfileId('owner');
|
||||
await s.saveHiddenLibrariesForProfile('owner', {'srv:movies'});
|
||||
await s.setActiveProfileId('kids');
|
||||
await s.saveHiddenLibrariesForProfile('kids', {'srv:kids'});
|
||||
|
||||
expect(s.getHiddenLibrariesForProfile('owner'), {'srv:movies'});
|
||||
expect(s.getHiddenLibrariesForProfile('kids'), {'srv:kids'});
|
||||
expect(s.getHiddenLibraries(), {'srv:kids'});
|
||||
});
|
||||
|
||||
test('overwrite replaces previous set', () async {
|
||||
final s = await StorageService.getInstance();
|
||||
await s.saveHiddenLibraries({'lib-a', 'lib-b'});
|
||||
|
||||
Reference in New Issue
Block a user