227 lines
7.4 KiB
Dart
227 lines
7.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/focus/input_mode_tracker.dart';
|
|
import 'package:plezy/i18n/strings.g.dart';
|
|
import 'package:plezy/media/media_backend.dart';
|
|
import 'package:plezy/media/media_item.dart';
|
|
import 'package:plezy/media/media_kind.dart';
|
|
import 'package:plezy/providers/multi_server_provider.dart';
|
|
import 'package:plezy/screens/music/now_playing_screen.dart';
|
|
import 'package:plezy/services/data_aggregation_service.dart';
|
|
import 'package:plezy/services/multi_server_manager.dart';
|
|
import 'package:plezy/services/music/music_playback_service.dart';
|
|
import 'package:plezy/theme/mono_theme.dart';
|
|
import 'package:plezy/utils/platform_detector.dart';
|
|
import 'package:provider/provider.dart';
|
|
|
|
import '../../test_helpers/media_items.dart';
|
|
|
|
MediaItem _track({required String id, required String title, required String album, required int year}) {
|
|
return testMediaItem(
|
|
id: id,
|
|
backend: MediaBackend.plex,
|
|
kind: MediaKind.track,
|
|
title: title,
|
|
parentId: 'album_$id',
|
|
parentTitle: album,
|
|
grandparentId: 'artist_$id',
|
|
grandparentTitle: 'Artist $id',
|
|
year: year,
|
|
durationMs: const Duration(minutes: 3).inMilliseconds,
|
|
serverId: 'server_1',
|
|
);
|
|
}
|
|
|
|
class _FakeMusicService extends StubMusicPlaybackService {
|
|
MediaItem track;
|
|
final MusicPlayContext context;
|
|
final StreamController<Duration> _positionController = StreamController<Duration>.broadcast(sync: true);
|
|
final List<Duration> seeks = [];
|
|
Duration _position = Duration.zero;
|
|
|
|
_FakeMusicService({required this.track, required this.context});
|
|
|
|
void advanceTo(MediaItem next) {
|
|
track = next;
|
|
_position = Duration.zero;
|
|
notifyListeners();
|
|
}
|
|
|
|
void emitPosition(Duration position) {
|
|
_position = position;
|
|
_positionController.add(position);
|
|
}
|
|
|
|
@override
|
|
bool get isAvailable => true;
|
|
|
|
@override
|
|
MediaItem get currentTrack => track;
|
|
|
|
@override
|
|
MusicPlaybackStatus get status => MusicPlaybackStatus.playing;
|
|
|
|
@override
|
|
Duration get position => _position;
|
|
|
|
@override
|
|
Stream<Duration> get positionStream => _positionController.stream;
|
|
|
|
@override
|
|
Duration get duration => const Duration(minutes: 3);
|
|
|
|
@override
|
|
List<MediaItem> get queue => [track];
|
|
|
|
@override
|
|
int get currentIndex => 0;
|
|
|
|
@override
|
|
MusicPlayContext get playContext => context;
|
|
|
|
@override
|
|
Future<void> seek(Duration position) async {
|
|
seeks.add(position);
|
|
_position = position;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_positionController.close();
|
|
super.dispose();
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
setUp(() {
|
|
LocaleSettings.setLocaleSync(AppLocale.en);
|
|
TvDetectionService.debugSetAppleTVOverride(false);
|
|
PlatformDetector.debugSetIsDesktopOSOverride(null);
|
|
});
|
|
|
|
tearDown(() {
|
|
TvDetectionService.debugSetAppleTVOverride(null);
|
|
PlatformDetector.debugSetIsDesktopOSOverride(null);
|
|
});
|
|
|
|
Future<void> pumpNowPlaying(WidgetTester tester, _FakeMusicService service, {required bool isTv}) async {
|
|
tester.view.devicePixelRatio = 1;
|
|
tester.view.physicalSize = const Size(1200, 700);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
|
|
TvDetectionService.debugSetAppleTVOverride(isTv);
|
|
PlatformDetector.debugSetIsDesktopOSOverride(!isTv);
|
|
|
|
final manager = MultiServerManager();
|
|
final multiServerProvider = MultiServerProvider(manager, DataAggregationService(manager));
|
|
addTearDown(service.dispose);
|
|
addTearDown(() {
|
|
multiServerProvider.dispose();
|
|
manager.dispose();
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
InputModeTracker(
|
|
child: TranslationProvider(
|
|
child: MultiProvider(
|
|
providers: [
|
|
ChangeNotifierProvider<MultiServerProvider>.value(value: multiServerProvider),
|
|
ChangeNotifierProvider<MusicPlaybackService>.value(value: service),
|
|
],
|
|
child: MaterialApp(
|
|
theme: monoTheme(dark: true).copyWith(platform: isTv ? TargetPlatform.android : TargetPlatform.windows),
|
|
home: const NowPlayingScreen(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pump();
|
|
}
|
|
|
|
for (final isTv in [false, true]) {
|
|
testWidgets('${isTv ? 'TV' : 'desktop'} follows the current album when an album queue crosses albums', (
|
|
tester,
|
|
) async {
|
|
final first = _track(id: 'one', title: 'First Track', album: 'First Album', year: 1973);
|
|
final second = _track(id: 'two', title: 'Second Track', album: 'Second Album', year: 1999);
|
|
final service = _FakeMusicService(
|
|
track: first,
|
|
context: const MusicPlayContext(id: 'album_one', title: 'First Album', kind: MusicPlayContextKind.album),
|
|
);
|
|
|
|
await pumpNowPlaying(tester, service, isTv: isTv);
|
|
|
|
expect(find.text(t.music.playingFrom(title: 'First Album · 1973')), findsOneWidget);
|
|
|
|
service.advanceTo(second);
|
|
await tester.pump();
|
|
|
|
expect(find.text(t.music.playingFrom(title: 'First Album · 1973')), findsNothing);
|
|
expect(find.text(t.music.playingFrom(title: 'Second Album · 1999')), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
testWidgets('playlist playback retains its queue provenance label', (tester) async {
|
|
final service = _FakeMusicService(
|
|
track: _track(id: 'one', title: 'First Track', album: 'First Album', year: 1973),
|
|
context: const MusicPlayContext(id: 'playlist_1', title: 'Road Trip', kind: MusicPlayContextKind.playlist),
|
|
);
|
|
|
|
await pumpNowPlaying(tester, service, isTv: false);
|
|
|
|
expect(find.text(t.music.playingFrom(title: 'Road Trip')), findsOneWidget);
|
|
expect(find.text(t.music.playingFrom(title: 'First Album · 1973')), findsNothing);
|
|
});
|
|
|
|
testWidgets('pending d-pad seek is discarded when the track changes', (tester) async {
|
|
final first = _track(id: 'one', title: 'First Track', album: 'First Album', year: 1973);
|
|
final second = _track(id: 'two', title: 'Second Track', album: 'Second Album', year: 1999);
|
|
final service = _FakeMusicService(
|
|
track: first,
|
|
context: const MusicPlayContext(title: 'Queue', kind: MusicPlayContextKind.tracks),
|
|
);
|
|
|
|
await pumpNowPlaying(tester, service, isTv: true);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp);
|
|
await tester.pump();
|
|
await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
|
|
|
service.advanceTo(second);
|
|
await tester.pump();
|
|
await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
await tester.pump();
|
|
|
|
expect(service.seeks, isEmpty);
|
|
});
|
|
|
|
testWidgets('seek progress resets immediately when the track changes', (tester) async {
|
|
final first = _track(id: 'one', title: 'First Track', album: 'First Album', year: 1973);
|
|
final second = _track(id: 'two', title: 'Second Track', album: 'Second Album', year: 1999);
|
|
final service = _FakeMusicService(
|
|
track: first,
|
|
context: const MusicPlayContext(title: 'Queue', kind: MusicPlayContextKind.tracks),
|
|
);
|
|
|
|
await pumpNowPlaying(tester, service, isTv: false);
|
|
service.emitPosition(const Duration(minutes: 2));
|
|
await tester.pump();
|
|
Slider seekSlider() => tester
|
|
.widgetList<Slider>(find.byType(Slider))
|
|
.singleWhere((slider) => slider.max == const Duration(minutes: 3).inMilliseconds);
|
|
|
|
expect(seekSlider().value, 120000);
|
|
|
|
service.advanceTo(second);
|
|
await tester.pump();
|
|
|
|
expect(seekSlider().value, 0);
|
|
});
|
|
}
|