Files
plezy/test/screens/libraries/libraries_screen_test.dart
T

209 lines
7.3 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/focus/input_mode_tracker.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/media/media_library.dart';
import 'package:plezy/mixins/refreshable.dart';
import 'package:plezy/providers/hidden_libraries_provider.dart';
import 'package:plezy/providers/libraries_provider.dart';
import 'package:plezy/providers/multi_server_provider.dart';
import 'package:plezy/screens/libraries/libraries_screen.dart';
import 'package:plezy/services/data_aggregation_service.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/services/storage_service.dart';
import 'package:plezy/theme/mono_theme.dart';
import 'package:plezy/utils/platform_detector.dart';
import 'package:provider/provider.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';
import 'package:shared_preferences_platform_interface/types.dart';
import '../../test_helpers/prefs.dart';
const _libraryA = MediaLibrary(
id: 'movies',
backend: MediaBackend.plex,
title: 'Library A',
kind: MediaKind.movie,
serverId: 'server',
);
const _libraryB = MediaLibrary(
id: 'shows',
backend: MediaBackend.plex,
title: 'Library B',
kind: MediaKind.show,
serverId: 'server',
);
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() {
resetSharedPreferencesForTest();
TvDetectionService.debugSetAppleTVOverride(false);
});
tearDown(() => TvDetectionService.debugSetAppleTVOverride(null));
testWidgets('stale saved tab cannot replace the current library tab', (tester) async {
final preferences = _GatedPreferences({
'selected_library_key': _libraryB.globalKey,
'library_tab_${_libraryA.globalKey}': LibraryTabType.playlists.name,
'library_tab_${_libraryB.globalKey}': LibraryTabType.browse.name,
});
final harness = await _Harness.create(preferences);
addTearDown(harness.dispose);
final selected = <String>[];
await harness.pump(tester, onLibrarySelected: selected.add);
expect(harness.controller(tester).index, 1);
preferences.blockNextSelectedLibraryWrite(_libraryA.globalKey);
final loadable = tester.state(find.byType(LibrariesScreen)) as LibraryLoadable;
loadable.loadLibraryByKey(_libraryA.globalKey);
await preferences.blocked;
loadable.loadLibraryByKey(_libraryB.globalKey);
await tester.pumpAndSettle();
expect(selected.last, _libraryB.globalKey);
expect(harness.controller(tester).index, 1);
preferences.release();
await tester.pumpAndSettle();
expect(selected.last, _libraryB.globalKey);
expect(harness.controller(tester).index, 1);
});
testWidgets('restoration applies a saved first tab', (tester) async {
final preferences = _GatedPreferences({
'selected_library_key': _libraryB.globalKey,
'library_tab_${_libraryA.globalKey}': LibraryTabType.recommended.name,
'library_tab_${_libraryB.globalKey}': LibraryTabType.browse.name,
});
final harness = await _Harness.create(preferences);
addTearDown(harness.dispose);
await harness.pump(tester);
expect(harness.controller(tester).index, 1);
final loadable = tester.state(find.byType(LibrariesScreen)) as LibraryLoadable;
loadable.loadLibraryByKey(_libraryA.globalKey);
await tester.pumpAndSettle();
expect(harness.controller(tester).index, 0);
});
testWidgets('disposal rejects a pending saved-tab continuation', (tester) async {
final preferences = _GatedPreferences({
'selected_library_key': _libraryB.globalKey,
'library_tab_${_libraryA.globalKey}': LibraryTabType.playlists.name,
});
final harness = await _Harness.create(preferences);
addTearDown(harness.dispose);
await harness.pump(tester);
preferences.blockNextSelectedLibraryWrite(_libraryA.globalKey);
final loadable = tester.state(find.byType(LibrariesScreen)) as LibraryLoadable;
loadable.loadLibraryByKey(_libraryA.globalKey);
await preferences.blocked;
await tester.pumpWidget(const SizedBox());
preferences.release();
await tester.pump();
expect(tester.takeException(), isNull);
});
}
final class _Harness {
_Harness({required this.libraries, required this.hiddenLibraries, required this.multiServer});
final LibrariesProvider libraries;
final HiddenLibrariesProvider hiddenLibraries;
final MultiServerProvider multiServer;
static Future<_Harness> create(_GatedPreferences preferences) async {
SharedPreferencesAsyncPlatform.instance = preferences;
await SettingsService.getInstance();
await StorageService.getInstance();
final libraries = LibrariesProvider();
await libraries.updateLibraryOrder(const [_libraryA, _libraryB]);
final hiddenLibraries = HiddenLibrariesProvider();
await hiddenLibraries.ensureInitialized();
final manager = MultiServerManager();
final multiServer = MultiServerProvider(manager, DataAggregationService(manager));
return _Harness(libraries: libraries, hiddenLibraries: hiddenLibraries, multiServer: multiServer);
}
Future<void> pump(WidgetTester tester, {ValueChanged<String>? onLibrarySelected}) async {
tester.view.physicalSize = const Size(1280, 720);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<LibrariesProvider>.value(value: libraries),
ChangeNotifierProvider<HiddenLibrariesProvider>.value(value: hiddenLibraries),
ChangeNotifierProvider<MultiServerProvider>.value(value: multiServer),
],
child: InputModeTracker(
child: MaterialApp(
theme: monoTheme(dark: true),
home: LibrariesScreen(onLibrarySelected: onLibrarySelected),
),
),
),
);
await tester.pumpAndSettle();
}
TabController controller(WidgetTester tester) {
final dynamic state = tester.state(find.byType(LibrariesScreen));
return state.tabController as TabController;
}
void dispose() {
libraries.dispose();
hiddenLibraries.dispose();
multiServer.dispose();
}
}
final class _GatedPreferences extends InMemorySharedPreferencesAsync {
_GatedPreferences(super.data) : super.withData();
String? _blockedValue;
Completer<void>? _entered;
Completer<void>? _release;
Future<void> get blocked => _entered!.future;
void blockNextSelectedLibraryWrite(String value) {
_blockedValue = value;
_entered = Completer<void>();
_release = Completer<void>();
}
void release() {
final release = _release;
if (release != null && !release.isCompleted) release.complete();
}
@override
Future<bool> setString(String key, String value, SharedPreferencesOptions options) async {
final result = await super.setString(key, value, options);
if (key.endsWith('selected_library_key') && value == _blockedValue) {
_blockedValue = null;
_entered!.complete();
await _release!.future;
}
return result;
}
}