| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- import 'dart:typed_data';
- import 'package:cardash_launcher/data/services/jellyfin_library_service.dart';
- import 'package:cardash_launcher/features/jellyfin/jellyfin_playback_controller.dart';
- import 'package:cardash_launcher/features/settings/jellyfin_controller.dart';
- import 'package:flutter_test/flutter_test.dart';
- import 'jellyfin_fakes.dart';
- class _Fixture {
- _Fixture({
- required this.jellyfin,
- required this.gateway,
- required this.library,
- required this.platform,
- required this.playback,
- });
- final JellyfinController jellyfin;
- final FakeJellyfinLibraryGateway gateway;
- final JellyfinLibraryService library;
- final FakeJellyfinPlaybackPlatform platform;
- final JellyfinPlaybackController playback;
- }
- Future<_Fixture> _fixture() async {
- final jellyfin = await configuredJellyfinController();
- final gateway = FakeJellyfinLibraryGateway()
- ..tracks = {
- 'album-1': [
- track('t1', albumId: 'album-1', albumName: 'Album 1', index: 1),
- track('t2', albumId: 'album-1', albumName: 'Album 1', index: 2),
- track('t3', albumId: 'album-1', albumName: 'Album 1', index: 3),
- ],
- 'album-2': [
- track('u1', albumId: 'album-2', albumName: 'Album 2', index: 1),
- track('u2', albumId: 'album-2', albumName: 'Album 2', index: 2),
- ],
- }
- ..artwork = Uint8List.fromList([1, 2, 3]);
- final library = testLibraryService(jellyfin, gateway);
- final platform = FakeJellyfinPlaybackPlatform();
- final playback = JellyfinPlaybackController(
- platform,
- library,
- jellyfin,
- pollInterval: const Duration(hours: 1),
- );
- return _Fixture(
- jellyfin: jellyfin,
- gateway: gateway,
- library: library,
- platform: platform,
- playback: playback,
- );
- }
- void main() {
- test('starts an album and exposes the current track', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- final error = await fixture.playback.playAlbum(album('album-1'));
- expect(error, isNull);
- expect(fixture.platform.setQueueCalls, 1);
- expect(fixture.playback.isActive, isTrue);
- expect(fixture.playback.playing, isTrue);
- expect(fixture.playback.currentItem?.trackId, 't1');
- expect(fixture.playback.mediaPlayback.title, 'Track t1');
- expect(fixture.playback.queueItems.length, 3);
- expect(fixture.platform.queue?.artwork, [1, 2, 3]);
- expect(
- fixture.platform.queue?.authorization,
- contains('Token="test-token"'),
- );
- });
- test('the same album is not rebuilt but a different album is', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- await fixture.playback.playAlbum(album('album-1'));
- expect(fixture.platform.setQueueCalls, 1);
- await fixture.playback.playAlbum(album('album-2'));
- expect(fixture.platform.setQueueCalls, 2);
- expect(fixture.playback.currentItem?.trackId, 'u1');
- });
- test('toggle pauses and resumes without losing the position', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- await fixture.playback.toggle();
- expect(fixture.playback.playing, isFalse);
- expect(fixture.platform.commands, contains('pause'));
- await fixture.playback.toggle();
- expect(fixture.playback.playing, isTrue);
- expect(fixture.platform.commands, contains('play'));
- });
- test('next and previous move through the queue', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- await fixture.playback.next();
- expect(fixture.playback.currentItem?.trackId, 't2');
- expect(fixture.playback.canGoPrevious, isTrue);
- await fixture.playback.previous();
- expect(fixture.playback.currentItem?.trackId, 't1');
- });
- test('selecting a track seeks to its queue index', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- await fixture.playback.playTrackAt(2);
- expect(fixture.platform.commands, contains('seekToIndex:2'));
- expect(fixture.playback.currentItem?.trackId, 't3');
- expect(fixture.playback.playing, isTrue);
- });
- test('tapping play after the end restarts at track one', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- fixture.platform.state = fixture.platform.state.copyWith(
- playing: false,
- positionMs: fixture.platform.state.durationMs,
- );
- await fixture.playback.restore();
- await fixture.playback.toggle();
- expect(fixture.platform.commands, contains('seekToIndex:0'));
- expect(fixture.playback.playing, isTrue);
- expect(fixture.playback.snapshot.positionMs, 0);
- });
- test('stop clears the queue and snapshot', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- await fixture.playback.stop();
- expect(fixture.playback.isActive, isFalse);
- expect(fixture.playback.queueItems, isEmpty);
- expect(fixture.playback.snapshot.available, isFalse);
- });
- test('notification permission delegates to the platform', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- expect(fixture.platform.permissionGranted, isFalse);
- expect(await fixture.playback.notificationPermissionGranted(), isFalse);
- expect(await fixture.playback.requestNotificationPermission(), isTrue);
- expect(fixture.platform.permissionGranted, isTrue);
- });
- test('media progress falls back to the queued track duration', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- await fixture.playback.playAlbum(album('album-1'));
- fixture.platform.zeroDuration = true;
- fixture.platform.state = fixture.platform.snapshotAt(
- 0,
- playing: true,
- positionMs: 30000,
- );
- await fixture.playback.toggle();
- expect(fixture.playback.snapshot.durationMs, 0);
- final media = fixture.playback.mediaPlayback;
- expect(media.durationMs, fixture.playback.currentItem!.durationMs);
- expect(media.positionMs, 30000);
- expect(media.progress, greaterThan(0));
- });
- test('a failed queue start surfaces an error', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- fixture.platform.setQueueResult = false;
- final error = await fixture.playback.playAlbum(album('album-1'));
- expect(error, isNotNull);
- expect(fixture.playback.error, isNotNull);
- expect(fixture.playback.isActive, isFalse);
- });
- test('an album with no playable tracks reports an error', () async {
- final fixture = await _fixture();
- addTearDown(fixture.playback.dispose);
- addTearDown(fixture.jellyfin.dispose);
- final error = await fixture.playback.playAlbum(album('missing'));
- expect(error, contains('No playable tracks'));
- expect(fixture.playback.error, contains('No playable tracks'));
- });
- }
|