fix: eliminate cross-app consistency drift
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:plezy/focus/focusable_text_field.dart';
|
||||
import 'package:plezy/services/gamepad_service.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
@@ -713,7 +714,7 @@ void main() {
|
||||
rebuild(() => onNavigateDown = () => navigateDownCalls++);
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(_tvKeyboardDoneKey(Icons.search_rounded));
|
||||
await tester.tap(_tvKeyboardDoneKey(Symbols.search_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(navigateDownCalls, 1);
|
||||
@@ -765,7 +766,7 @@ void main() {
|
||||
});
|
||||
await tester.pump();
|
||||
|
||||
await tester.tap(_tvKeyboardDoneKey(Icons.search_rounded));
|
||||
await tester.tap(_tvKeyboardDoneKey(Symbols.search_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(submitted, 'query');
|
||||
@@ -799,7 +800,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byKey(const Key('tv_virtual_keyboard_panel')), findsOneWidget);
|
||||
|
||||
await tester.tap(_tvKeyboardDoneKey(Icons.search_rounded));
|
||||
await tester.tap(_tvKeyboardDoneKey(Symbols.search_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.pump();
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:plezy/connection/connection.dart';
|
||||
import 'package:plezy/connection/connection_registry.dart';
|
||||
import 'package:plezy/database/app_database.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/navigation/profile_navigation_scope.dart';
|
||||
import 'package:plezy/media/ids.dart';
|
||||
import 'package:plezy/media/library_query.dart';
|
||||
import 'package:plezy/media/media_backend.dart';
|
||||
@@ -27,12 +28,19 @@ import 'package:plezy/profiles/active_profile_provider.dart';
|
||||
import 'package:plezy/profiles/plex_home_service.dart';
|
||||
import 'package:plezy/profiles/profile_connection_registry.dart';
|
||||
import 'package:plezy/profiles/profile_registry.dart';
|
||||
import 'package:plezy/providers/download_provider.dart';
|
||||
import 'package:plezy/providers/multi_server_provider.dart';
|
||||
import 'package:plezy/screens/music/album_detail_screen.dart';
|
||||
import 'package:plezy/screens/music/artist_detail_screen.dart';
|
||||
import 'package:plezy/services/data_aggregation_service.dart';
|
||||
import 'package:plezy/services/download_manager_service.dart';
|
||||
import 'package:plezy/services/download_storage_service.dart';
|
||||
import 'package:plezy/services/jellyfin_client.dart';
|
||||
import 'package:plezy/services/jellyfin_api_cache.dart';
|
||||
import 'package:plezy/services/music/music_playback_service.dart';
|
||||
import 'package:plezy/services/multi_server_manager.dart';
|
||||
import 'package:plezy/services/plex_api_cache.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/services/plex_client.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/utils/media_server_http_client.dart';
|
||||
@@ -40,6 +48,7 @@ import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/media_context_menu.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
import '../test_helpers/prefs.dart';
|
||||
|
||||
void main() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -303,6 +312,102 @@ void main() {
|
||||
expect(find.text(t.playlists.selectPlaylist), findsNothing);
|
||||
expect(find.text('picker target'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('track album action uses the profile navigator from the sibling menu overlay', (tester) async {
|
||||
final track = testMediaItem(
|
||||
id: 'track-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.track,
|
||||
title: 'Track',
|
||||
parentId: 'album-1',
|
||||
parentTitle: 'Album',
|
||||
grandparentId: 'artist-1',
|
||||
grandparentTitle: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final album = testMediaItem(
|
||||
id: 'album-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.album,
|
||||
title: 'Album',
|
||||
parentId: 'artist-1',
|
||||
parentTitle: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final harness = await _pumpSiblingMusicMenu(tester, item: track, relatedItems: [album]);
|
||||
|
||||
await _selectSiblingMusicMenuAction(tester, harness, t.music.goToAlbum);
|
||||
|
||||
expect(find.byType(AlbumDetailScreen), findsOneWidget);
|
||||
expect(harness.profileNavigatorKey.currentState!.canPop(), isTrue);
|
||||
expect(harness.rootNavigatorKey.currentState!.canPop(), isFalse);
|
||||
expect(
|
||||
Provider.of<MusicPlaybackService>(tester.element(find.byType(AlbumDetailScreen)), listen: false),
|
||||
same(harness.music),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('album artist action uses the profile navigator from the sibling menu overlay', (tester) async {
|
||||
final album = testMediaItem(
|
||||
id: 'album-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.album,
|
||||
title: 'Album',
|
||||
parentId: 'artist-1',
|
||||
parentTitle: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final artist = testMediaItem(
|
||||
id: 'artist-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.artist,
|
||||
title: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final harness = await _pumpSiblingMusicMenu(tester, item: album, relatedItems: [artist]);
|
||||
|
||||
await _selectSiblingMusicMenuAction(tester, harness, t.music.goToArtist);
|
||||
|
||||
expect(find.byType(ArtistDetailScreen), findsOneWidget);
|
||||
expect(harness.profileNavigatorKey.currentState!.canPop(), isTrue);
|
||||
expect(harness.rootNavigatorKey.currentState!.canPop(), isFalse);
|
||||
expect(
|
||||
Provider.of<MusicPlaybackService>(tester.element(find.byType(ArtistDetailScreen)), listen: false),
|
||||
same(harness.music),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('track artist action uses the profile navigator from the sibling menu overlay', (tester) async {
|
||||
final track = testMediaItem(
|
||||
id: 'track-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.track,
|
||||
title: 'Track',
|
||||
parentId: 'album-1',
|
||||
parentTitle: 'Album',
|
||||
grandparentId: 'artist-1',
|
||||
grandparentTitle: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final artist = testMediaItem(
|
||||
id: 'artist-1',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.artist,
|
||||
title: 'Artist',
|
||||
serverId: 'srv-1',
|
||||
);
|
||||
final harness = await _pumpSiblingMusicMenu(tester, item: track, relatedItems: [artist]);
|
||||
|
||||
await _selectSiblingMusicMenuAction(tester, harness, t.music.goToArtist);
|
||||
|
||||
expect(find.byType(ArtistDetailScreen), findsOneWidget);
|
||||
expect(harness.profileNavigatorKey.currentState!.canPop(), isTrue);
|
||||
expect(harness.rootNavigatorKey.currentState!.canPop(), isFalse);
|
||||
expect(
|
||||
Provider.of<MusicPlaybackService>(tester.element(find.byType(ArtistDetailScreen)), listen: false),
|
||||
same(harness.music),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -467,6 +572,170 @@ class _RecordingMusicPlaybackService extends StubMusicPlaybackService {
|
||||
}
|
||||
}
|
||||
|
||||
class _RelatedMusicClient implements MediaServerClient {
|
||||
_RelatedMusicClient(Iterable<MediaItem> items) : _items = {for (final item in items) item.id: item};
|
||||
|
||||
final Map<String, MediaItem> _items;
|
||||
|
||||
@override
|
||||
ServerId get serverId => ServerId('srv-1');
|
||||
|
||||
@override
|
||||
String? get serverName => 'Server';
|
||||
|
||||
@override
|
||||
MediaBackend get backend => MediaBackend.jellyfin;
|
||||
|
||||
@override
|
||||
ServerCapabilities get capabilities => ServerCapabilities.jellyfin;
|
||||
|
||||
@override
|
||||
Future<MediaItem?> fetchItem(String id) async => _items[id];
|
||||
|
||||
@override
|
||||
Future<List<MediaItem>> fetchAlbumTracks(String albumId) async => const [];
|
||||
|
||||
@override
|
||||
Future<List<MediaItem>> fetchArtistAlbums(String artistId) async => const [];
|
||||
|
||||
@override
|
||||
void close() {}
|
||||
|
||||
@override
|
||||
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
|
||||
}
|
||||
|
||||
class _SiblingMusicMenuHarness {
|
||||
const _SiblingMusicMenuHarness({
|
||||
required this.rootNavigatorKey,
|
||||
required this.profileNavigatorKey,
|
||||
required this.menuKey,
|
||||
required this.music,
|
||||
});
|
||||
|
||||
final GlobalKey<NavigatorState> rootNavigatorKey;
|
||||
final GlobalKey<NavigatorState> profileNavigatorKey;
|
||||
final GlobalKey<MediaContextMenuState> menuKey;
|
||||
final _RecordingMusicPlaybackService music;
|
||||
}
|
||||
|
||||
Future<_SiblingMusicMenuHarness> _pumpSiblingMusicMenu(
|
||||
WidgetTester tester, {
|
||||
required MediaItem item,
|
||||
required List<MediaItem> relatedItems,
|
||||
}) async {
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
await SettingsService.getInstance();
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
|
||||
final db = AppDatabase.forTesting(NativeDatabase.memory());
|
||||
JellyfinApiCache.initialize(db);
|
||||
final downloadManager = DownloadManagerService(
|
||||
database: db,
|
||||
storageService: DownloadStorageService.instance,
|
||||
clientResolver: (serverId, {clientScopeId}) => null,
|
||||
);
|
||||
downloadManager.recoveryFuture = Future<void>.value();
|
||||
final downloadProvider = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
|
||||
await downloadProvider.ensureInitialized();
|
||||
final client = _RelatedMusicClient(relatedItems);
|
||||
final manager = MultiServerManager()..debugRegisterClientForTesting(client);
|
||||
final multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager));
|
||||
final connections = ConnectionRegistry(db);
|
||||
final profileConnections = ProfileConnectionRegistry(db);
|
||||
final plexHome = PlexHomeService(
|
||||
connections: connections,
|
||||
profileConnections: profileConnections,
|
||||
plexHomeUserFetcher: (_) async => const [],
|
||||
);
|
||||
final activeProfileProvider = ActiveProfileProvider(
|
||||
registry: ProfileRegistry(db),
|
||||
plexHome: plexHome,
|
||||
connections: connections,
|
||||
);
|
||||
final music = _RecordingMusicPlaybackService();
|
||||
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final profileNavigatorKey = GlobalKey<NavigatorState>();
|
||||
final menuKey = GlobalKey<MediaContextMenuState>();
|
||||
|
||||
addTearDown(() async {
|
||||
downloadProvider.dispose();
|
||||
downloadManager.dispose();
|
||||
activeProfileProvider.dispose();
|
||||
await plexHome.dispose();
|
||||
music.dispose();
|
||||
multiServerProvider.dispose();
|
||||
manager.dispose();
|
||||
await db.close();
|
||||
});
|
||||
|
||||
await tester.pumpWidget(
|
||||
TranslationProvider(
|
||||
child: MaterialApp(
|
||||
navigatorKey: rootNavigatorKey,
|
||||
theme: monoTheme(dark: true).copyWith(platform: TargetPlatform.macOS),
|
||||
home: MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider<MultiServerProvider>.value(value: multiServerProvider),
|
||||
ChangeNotifierProvider<DownloadProvider>.value(value: downloadProvider),
|
||||
ChangeNotifierProvider<ActiveProfileProvider>.value(value: activeProfileProvider),
|
||||
ChangeNotifierProvider<MusicPlaybackService>.value(value: music),
|
||||
],
|
||||
child: ProfileNavigationScope(
|
||||
navigatorKey: profileNavigatorKey,
|
||||
routeObserver: RouteObserver<PageRoute<dynamic>>(),
|
||||
mainScaffoldMessengerKey: GlobalKey<ScaffoldMessengerState>(),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Navigator(
|
||||
key: profileNavigatorKey,
|
||||
onGenerateRoute: (_) => MaterialPageRoute<void>(
|
||||
builder: (_) => const Scaffold(body: Center(child: Text('profile content'))),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Material(
|
||||
child: MediaContextMenu(
|
||||
key: menuKey,
|
||||
item: item,
|
||||
child: const SizedBox(
|
||||
width: 180,
|
||||
height: 64,
|
||||
child: Center(child: Text('mini-player menu target')),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return _SiblingMusicMenuHarness(
|
||||
rootNavigatorKey: rootNavigatorKey,
|
||||
profileNavigatorKey: profileNavigatorKey,
|
||||
menuKey: menuKey,
|
||||
music: music,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _selectSiblingMusicMenuAction(
|
||||
WidgetTester tester,
|
||||
_SiblingMusicMenuHarness harness,
|
||||
String actionLabel,
|
||||
) async {
|
||||
harness.menuKey.currentState!.showContextMenu(tester.element(find.text('mini-player menu target')));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text(actionLabel));
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
PlexHomeUser _homeUser({required bool admin}) {
|
||||
return PlexHomeUser(
|
||||
id: 0,
|
||||
|
||||
@@ -292,8 +292,11 @@ void main() {
|
||||
await tester.sendKeyUpEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text(t.common.play), findsOneWidget);
|
||||
final menuSurface = find.byWidgetPredicate((widget) => widget is Material && widget.elevation == 3);
|
||||
final playMenuIcon = find.byWidgetPredicate(
|
||||
(widget) => widget is AppIcon && widget.icon == Symbols.play_arrow_rounded,
|
||||
);
|
||||
expect(playMenuIcon, findsOneWidget);
|
||||
final menuSurface = find.ancestor(of: playMenuIcon, matching: find.byType(BottomSheet));
|
||||
expect(menuSurface, findsOneWidget);
|
||||
expect(tester.getCenter(menuSurface).dx, closeTo(cardRect.center.dx, 0.1));
|
||||
|
||||
@@ -301,18 +304,23 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('stays hidden while the route observer suppresses it', (tester) async {
|
||||
testWidgets('stays hidden while a named suppressing route is active', (tester) async {
|
||||
final service = _FakeMusicService(track: _track);
|
||||
final observer = MusicUiRouteObserver();
|
||||
observer.suppress.value = true;
|
||||
|
||||
await tester.pumpWidget(wrap(service: service, observer: observer));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Dawn'), findsOneWidget);
|
||||
|
||||
final route = MaterialPageRoute<void>(
|
||||
settings: const RouteSettings(name: kNowPlayingRouteName),
|
||||
builder: (_) => const SizedBox.shrink(),
|
||||
);
|
||||
observer.didPush(route, null);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Dawn'), findsNothing);
|
||||
|
||||
// Suppression lifting brings it back without a rebuild from the service.
|
||||
observer.suppress.value = false;
|
||||
observer.didRemove(route, null);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Dawn'), findsOneWidget);
|
||||
});
|
||||
|
||||
@@ -9,28 +9,49 @@ import 'package:plezy/utils/media_image_helper.dart';
|
||||
import 'package:plezy/widgets/optimized_media_image.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('network images use decode resize without disk cache resize', (tester) async {
|
||||
testWidgets('network widget matches the shared server artwork provider', (tester) async {
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(1280, 720);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
const imageUrl = 'https://example.invalid/episode-thumb.jpg';
|
||||
|
||||
await tester.pumpWidget(
|
||||
const MaterialApp(
|
||||
home: SizedBox(
|
||||
width: 160,
|
||||
height: 90,
|
||||
child: OptimizedMediaImage.thumb(
|
||||
imagePath: 'https://example.invalid/episode-thumb.jpg',
|
||||
width: 160,
|
||||
height: 90,
|
||||
),
|
||||
child: OptimizedMediaImage.thumb(imagePath: imageUrl, width: 160, height: 90),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final image = tester.widget<Image>(find.byType(Image));
|
||||
final resizeProvider = image.image as ResizeImage;
|
||||
final cachedProvider = resizeProvider.imageProvider as CachedNetworkImageProvider;
|
||||
final widgetProvider = image.image as ResizeImage;
|
||||
final widgetCached = widgetProvider.imageProvider as CachedNetworkImageProvider;
|
||||
final sharedProvider =
|
||||
MediaImageHelper.serverArtworkProvider(imageUrl: imageUrl, memWidth: 200, memHeight: 180) as ResizeImage;
|
||||
final sharedCached = sharedProvider.imageProvider as CachedNetworkImageProvider;
|
||||
|
||||
expect(resizeProvider.height, isNotNull);
|
||||
expect(cachedProvider.maxHeight, isNull);
|
||||
expect(cachedProvider.maxWidth, isNull);
|
||||
expect(widgetCached, sharedCached);
|
||||
expect(widgetProvider.width, sharedProvider.width);
|
||||
expect(widgetProvider.width, 200);
|
||||
expect(widgetProvider.height, sharedProvider.height);
|
||||
expect(widgetProvider.height, 180);
|
||||
expect(widgetProvider.policy, sharedProvider.policy);
|
||||
expect(widgetProvider.policy, ResizeImagePolicy.fit);
|
||||
expect(widgetProvider.allowUpscaling, sharedProvider.allowUpscaling);
|
||||
expect(widgetProvider.allowUpscaling, isFalse);
|
||||
expect(widgetCached.url, sharedCached.url);
|
||||
expect(widgetCached.url, imageUrl);
|
||||
expect(widgetCached.cacheKey, sharedCached.cacheKey);
|
||||
expect(widgetCached.cacheKey, isNotNull);
|
||||
expect(widgetCached.headers, sharedCached.headers);
|
||||
expect(widgetCached.headers, const {'User-Agent': 'Plezy'});
|
||||
expect(widgetCached.maxWidth, sharedCached.maxWidth);
|
||||
expect(widgetCached.maxWidth, isNull);
|
||||
expect(widgetCached.maxHeight, sharedCached.maxHeight);
|
||||
expect(widgetCached.maxHeight, isNull);
|
||||
});
|
||||
|
||||
testWidgets('failed image placeholders keep explicit dimensions in loose layouts', (tester) async {
|
||||
@@ -69,6 +90,10 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('same local artwork path re-resolves after the file appears', (tester) async {
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.view.physicalSize = const Size(1280, 720);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
final directory = Directory.systemTemp.createTempSync('plezy-image-test');
|
||||
addTearDown(() => directory.deleteSync(recursive: true));
|
||||
final file = File('${directory.path}/poster.png');
|
||||
@@ -110,6 +135,13 @@ void main() {
|
||||
expect(file.existsSync(), isTrue);
|
||||
expect(find.byIcon(Symbols.image_not_supported_rounded), findsNothing);
|
||||
expect(find.byType(Image), findsOneWidget);
|
||||
final localImage = tester.widget<Image>(find.byType(Image));
|
||||
final localResize = localImage.image as ResizeImage;
|
||||
final localProvider = localResize.imageProvider as FileImage;
|
||||
expect(localResize.width, 120);
|
||||
expect(localResize.height, 180);
|
||||
expect(localResize.policy, ResizeImagePolicy.fit);
|
||||
expect(localProvider.file.path, file.path);
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:plezy/screens/profile/pin_entry_dialog.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
|
||||
@@ -197,8 +198,8 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
expect(find.byIcon(Icons.close_rounded), findsOneWidget);
|
||||
expect(find.byIcon(Icons.backspace_outlined), findsOneWidget);
|
||||
expect(find.byIcon(Symbols.close_rounded), findsOneWidget);
|
||||
expect(find.byIcon(Symbols.backspace_rounded), findsOneWidget);
|
||||
expect(find.byType(TextField), findsNothing);
|
||||
expect(find.widgetWithText(TextButton, 'Cancel'), findsNothing);
|
||||
expect(find.byType(FilledButton), findsNothing);
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/providers/companion_remote_provider.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/widgets/companion_remote/remote_session_dialog.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -106,6 +107,7 @@ Future<void> _pumpDialogLauncher(WidgetTester tester, CompanionRemoteProvider pr
|
||||
ChangeNotifierProvider<CompanionRemoteProvider>.value(
|
||||
value: provider,
|
||||
child: MaterialApp(
|
||||
theme: monoTheme(dark: true),
|
||||
home: Builder(
|
||||
builder: (context) {
|
||||
return Scaffold(
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:plezy/focus/dpad_navigator.dart';
|
||||
import 'package:plezy/utils/platform_detector.dart';
|
||||
import 'package:plezy/widgets/tv_virtual_keyboard.dart';
|
||||
@@ -153,9 +154,9 @@ void main() {
|
||||
await _pumpKeyboard(tester, controller: controller);
|
||||
|
||||
expect(find.text('='), findsOneWidget);
|
||||
expect(find.byIcon(Icons.functions_rounded), findsOneWidget);
|
||||
expect(find.byIcon(Symbols.functions_rounded), findsOneWidget);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.functions_rounded));
|
||||
await tester.tap(find.byIcon(Symbols.functions_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('ABC'), findsOneWidget);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/i18n/strings.g.dart';
|
||||
import 'package:plezy/mpv/models.dart';
|
||||
import 'package:plezy/mpv/player/player.dart';
|
||||
import 'package:plezy/mpv/player/player_state.dart';
|
||||
import 'package:plezy/mpv/player/player_streams.dart';
|
||||
import 'package:plezy/screens/settings/subtitle_styling_screen.dart';
|
||||
import 'package:plezy/services/sleep_timer_service.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/theme/mono_tokens.dart';
|
||||
import 'package:plezy/widgets/video_controls/sheets/video_settings_sheet.dart';
|
||||
@@ -30,12 +33,24 @@ const _testTokens = MonoTokens(
|
||||
);
|
||||
|
||||
void main() {
|
||||
setUpAll(() async {
|
||||
await LocaleSettings.setLocale(AppLocale.ru);
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
});
|
||||
|
||||
setUp(() async {
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
SleepTimerService().cancelTimer();
|
||||
resetSharedPreferencesForTest();
|
||||
SettingsService.resetForTesting();
|
||||
await SettingsService.getInstance();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
SleepTimerService().cancelTimer();
|
||||
LocaleSettings.setLocaleSync(AppLocale.en);
|
||||
});
|
||||
|
||||
testWidgets('shows audio passthrough on supported TV-style surfaces', (tester) async {
|
||||
await _pumpSheet(tester);
|
||||
|
||||
@@ -43,9 +58,45 @@ void main() {
|
||||
|
||||
expect(find.text('Audio Passthrough'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('localizes Off, Normal, and Active video setting values', (tester) async {
|
||||
LocaleSettings.setLocaleSync(AppLocale.ru);
|
||||
|
||||
await _pumpSheet(tester, canControl: true);
|
||||
expect(find.text('Обычная'), findsOneWidget);
|
||||
expect(find.text('Выкл.'), findsOneWidget);
|
||||
|
||||
final sleepTimer = SleepTimerService();
|
||||
sleepTimer.startTimer(const Duration(hours: 1), () {});
|
||||
try {
|
||||
await tester.pump();
|
||||
expect(find.textContaining('Активен ('), findsOneWidget);
|
||||
} finally {
|
||||
sleepTimer.cancelTimer();
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('localizes every ASS subtitle override enum label', (tester) async {
|
||||
LocaleSettings.setLocaleSync(AppLocale.ru);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(extensions: const [_testTokens]),
|
||||
home: const SubtitleStylingScreen(),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Переопределение ASS'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final dialog = find.byType(AlertDialog);
|
||||
for (final label in ['Нет', 'Да', 'Масштабировать', 'Принудительно', 'Удалить стили']) {
|
||||
expect(find.descendant(of: dialog, matching: find.text(label)), findsOneWidget);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _pumpSheet(WidgetTester tester) async {
|
||||
Future<void> _pumpSheet(WidgetTester tester, {bool canControl = false}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(extensions: const [_testTokens]),
|
||||
@@ -57,7 +108,7 @@ Future<void> _pumpSheet(WidgetTester tester) async {
|
||||
player: _FakeSettingsPlayer(),
|
||||
audioSyncOffset: 0,
|
||||
subtitleSyncOffset: 0,
|
||||
canControl: false,
|
||||
canControl: canControl,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user