refactor(features): consolidate shared feature primitives

This commit is contained in:
edde746
2026-07-12 17:31:13 +02:00
parent 6506f36843
commit 7ecadffdb1
103 changed files with 1718 additions and 898 deletions
@@ -10,6 +10,7 @@ import 'package:plezy/services/catalog/catalog_source.dart';
import 'package:plezy/services/catalog/trakt_catalog_source.dart';
import 'package:plezy/services/trackers/tracker_session.dart';
import 'package:plezy/services/trakt/trakt_client.dart';
import '../../test_helpers/media_items.dart';
TrackerSession _session() {
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
@@ -105,7 +106,7 @@ void main() {
expect(show.rating, 8.5);
expect(page.items[0].airStatus, isNull);
final rendered = page.items[0].toMediaItem();
final rendered = page.items[0].totestMediaItem();
expect(rendered.serverId, isNull);
expect(rendered.title, 'The Matrix');
expect(rendered.isCatalogItem, isTrue);
@@ -0,0 +1,50 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/models/companion_remote/remote_command.dart';
import 'package:plezy/services/companion_remote/companion_remote_peer_service.dart';
import 'package:plezy/services/companion_remote/remote_auth_context.dart';
void main() {
test('host and remote dispatch encrypted commands through the same contract', () async {
final host = CompanionRemotePeerService();
final remote = CompanionRemotePeerService();
addTearDown(() async {
await remote.dispose();
await host.dispose();
});
final context = RemoteAuthContext(
id: 'context-1',
backend: 'plex',
connectionId: 'connection-1',
homeSecret: List<int>.generate(32, (index) => index),
discoveryKey: List<int>.generate(32, (index) => 255 - index),
clientIdentifier: 'host-client',
userUuid: 'user-1',
allowedUserUuids: const ['user-1'],
);
final session = await host.createSessionForContexts('Test Host', 'macos', [context]);
await remote.joinSessionWithContexts(
'Test Remote',
'ios',
'127.0.0.1:${session.port}',
[context],
authContextId: context.id,
expectedHostClientId: context.clientIdentifier,
);
final hostCommand = host.onCommandReceived.firstWhere((command) => command.type == RemoteCommandType.play);
remote.sendCommand(const RemoteCommand(type: RemoteCommandType.play, data: {'source': 'remote'}));
expect(
await hostCommand.timeout(const Duration(seconds: 5)),
const RemoteCommand(type: RemoteCommandType.play, data: {'source': 'remote'}),
);
final remoteCommand = remote.onCommandReceived.firstWhere((command) => command.type == RemoteCommandType.pause);
host.sendCommand(const RemoteCommand(type: RemoteCommandType.pause, data: {'source': 'host'}));
expect(
await remoteCommand.timeout(const Duration(seconds: 5)),
const RemoteCommand(type: RemoteCommandType.pause, data: {'source': 'host'}),
);
});
}
@@ -20,6 +20,7 @@ import 'package:plezy/utils/media_server_http_client.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
class _DelayedCountingHttpClient extends http.BaseClient {
_DelayedCountingHttpClient(this.body);
@@ -54,7 +55,7 @@ void main() {
});
test('buildArtworkSpecs includes all standard artwork with sanitized local keys', () {
final item = MediaItem(
final item = testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -29,6 +29,7 @@ import 'package:saf_util/saf_util_platform_interface.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
void main() {
group('downloadExtensionFromUrl', () {
@@ -140,7 +141,7 @@ void main() {
clientResolver: (serverId, {clientScopeId}) => null,
);
final year = await manager.debugResolveSafRecoveryShowYear(
MediaItem(
testMediaItem(
id: 'ep-1',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -245,7 +246,7 @@ void main() {
final client = _ArtworkRepairClient(
serverId: ServerId('srv'),
items: {
'show-1': MediaItem(
'show-1': testMediaItem(
id: 'show-1',
backend: MediaBackend.plex,
kind: MediaKind.show,
@@ -562,7 +563,7 @@ Future<_DeletionResult> _runEpisodeDeletion({required bool saf, bool failVideoDe
JellyfinApiCache.initialize(db);
final serverId = ServerId('srv');
const globalKey = 'srv:episode-1';
final episode = MediaItem(
final episode = testMediaItem(
id: 'episode-1',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -674,7 +675,7 @@ Future<_ContainerDeletionResult> _runContainerDeletion({required MediaKind kind,
final serverId = ServerId('srv');
final id = '${kind.id}-1';
final globalKey = 'srv:$id';
final metadata = MediaItem(
final metadata = testMediaItem(
id: id,
backend: MediaBackend.plex,
kind: kind,
@@ -913,7 +914,7 @@ DownloadTask _downloadTask(String taskId, String globalKey) {
}
MediaItem _movie({String? thumbPath}) {
return MediaItem(
return testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -12,6 +12,7 @@ import 'package:plezy/services/settings_service.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
void main() {
late Directory tmpRoot;
@@ -558,7 +559,7 @@ void main() {
// ============================================================
MediaItem _movie({required String title, int? year}) {
return MediaItem(
return testMediaItem(
id: 'm-${title.hashCode}',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -568,7 +569,7 @@ MediaItem _movie({required String title, int? year}) {
}
MediaItem _show({required String title, int? year}) {
return MediaItem(
return testMediaItem(
id: 's-${title.hashCode}',
backend: MediaBackend.plex,
kind: MediaKind.show,
@@ -578,7 +579,7 @@ MediaItem _show({required String title, int? year}) {
}
MediaItem _season({required String showTitle, int? showYear, required int seasonNumber}) {
return MediaItem(
return testMediaItem(
id: 'season-$showTitle-$seasonNumber',
backend: MediaBackend.plex,
kind: MediaKind.season,
@@ -596,7 +597,7 @@ MediaItem _episode({
required int episodeNumber,
required String episodeTitle,
}) {
return MediaItem(
return testMediaItem(
id: 'ep-$showTitle-$seasonNumber-$episodeNumber',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -12,6 +12,7 @@ import 'package:plezy/services/data_aggregation_service.dart';
import 'package:plezy/services/episode_navigation_service.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:provider/provider.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// `EpisodeNavigationService` has two methods:
@@ -31,9 +32,9 @@ import 'package:provider/provider.dart';
// the public surface callers depend on.
MediaItem _meta(String id, {String? title}) =>
MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.episode, title: title ?? 'Episode $id');
testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.episode, title: title ?? 'Episode $id');
MediaItem _jfEpisode(String id, {required String seriesId, ServerId? serverId}) => MediaItem(
MediaItem _jfEpisode(String id, {required String seriesId, ServerId? serverId}) => testMediaItem(
id: id,
backend: MediaBackend.jellyfin,
kind: MediaKind.episode,
@@ -12,6 +12,7 @@ import 'package:plezy/services/external_player_service.dart';
import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/offline_watch_sync_service.dart';
import '../test_helpers/media_items.dart';
class _RecordingClient implements MediaServerClient {
_RecordingClient({this.backend = MediaBackend.plex});
@@ -77,7 +78,7 @@ class _RecordingClient implements MediaServerClient {
}
MediaItem _item({int? durationMs}) {
return MediaItem(
return testMediaItem(
id: 'item-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
+85 -19
View File
@@ -16,6 +16,7 @@ import 'package:plezy/utils/device_identity.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/paged_fakes.dart';
import '../test_helpers/media_items.dart';
JellyfinConnection _conn({String accessToken = 'tok-abc', String baseUrl = 'https://jf.example.com'}) =>
testJellyfinConnection(
@@ -423,7 +424,7 @@ void main() {
addTearDown(scoped.close);
final resolution = await scoped.resolveDownload(
MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
mediaIndex: 1,
);
@@ -470,7 +471,7 @@ void main() {
addTearDown(scoped.close);
final url = await scoped.resolveExternalPlaybackUrl(
MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
mediaIndex: 0,
mediaSourceId: 'item-1',
);
@@ -535,7 +536,7 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -611,7 +612,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
qualityPreset: TranscodeQualityPreset.p720_2mbps,
),
@@ -694,7 +700,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
),
);
@@ -795,7 +806,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
),
);
@@ -857,7 +873,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
),
);
@@ -918,7 +939,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
selectedAudioStreamId: 4,
),
@@ -986,7 +1012,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 1,
selectedAudioStreamId: 4,
),
@@ -1048,7 +1079,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
selectedMediaSourceId: 'src-1080',
),
@@ -1108,7 +1144,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
selectedMediaSourceId: 'item-1',
),
@@ -1176,7 +1217,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
selectedMediaSourceId: 'src-1080',
),
@@ -1265,7 +1311,7 @@ void main() {
);
addTearDown(scoped.close);
final item = MediaItem(
final item = testMediaItem(
id: 'folder/item #1?x',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -1303,7 +1349,12 @@ void main() {
);
addTearDown(scoped.close);
final item = MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1');
final item = testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
);
await expectLater(scoped.removeFromContinueWatching(item), throwsA(isA<UnsupportedError>()));
expect(requested, isFalse);
@@ -1345,7 +1396,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
qualityPreset: TranscodeQualityPreset.p720_2mbps,
),
@@ -1386,7 +1442,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
),
);
@@ -1578,7 +1639,12 @@ void main() {
final result = await scoped.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-1'),
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-1',
),
selectedMediaIndex: 0,
qualityPreset: TranscodeQualityPreset.p720_2mbps,
),
@@ -1883,7 +1949,7 @@ void main() {
addTearDown(scoped.close);
final items = await scoped.fetchFolderChildren(
MediaItem(id: 'folder-1', backend: MediaBackend.jellyfin, kind: MediaKind.folder),
testMediaItem(id: 'folder-1', backend: MediaBackend.jellyfin, kind: MediaKind.folder),
onPage: pages.add,
);
@@ -1925,7 +1991,7 @@ void main() {
addTearDown(scoped.close);
final items = await scoped.fetchFolderChildren(
MediaItem(id: 'season-1', backend: MediaBackend.jellyfin, kind: MediaKind.season),
testMediaItem(id: 'season-1', backend: MediaBackend.jellyfin, kind: MediaKind.season),
onPage: pages.add,
);
@@ -14,6 +14,7 @@ import 'package:plezy/services/playlist_items_loader.dart';
import 'package:plezy/utils/media_server_http_client.dart';
import '../test_helpers/paged_fakes.dart';
import '../test_helpers/media_items.dart';
/// Recording fake that satisfies [JellyfinClient] via `implements` +
/// `noSuchMethod`. The launcher only needs the
@@ -76,7 +77,7 @@ class _RecordingJellyfinClient implements JellyfinClient {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
MediaItem _ep(String id, {ServerId? serverId}) => MediaItem(
MediaItem _ep(String id, {ServerId? serverId}) => testMediaItem(
id: id,
backend: MediaBackend.jellyfin,
kind: MediaKind.episode,
@@ -84,7 +85,7 @@ MediaItem _ep(String id, {ServerId? serverId}) => MediaItem(
serverId: serverId ?? ServerId('srv-jf'),
);
MediaItem _movie(String id, {ServerId? serverId}) => MediaItem(
MediaItem _movie(String id, {ServerId? serverId}) => testMediaItem(
id: id,
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -92,7 +93,7 @@ MediaItem _movie(String id, {ServerId? serverId}) => MediaItem(
serverId: serverId ?? ServerId('srv-jf'),
);
MediaItem _clip(String id, {ServerId? serverId}) => MediaItem(
MediaItem _clip(String id, {ServerId? serverId}) => testMediaItem(
id: id,
backend: MediaBackend.jellyfin,
kind: MediaKind.clip,
@@ -100,7 +101,7 @@ MediaItem _clip(String id, {ServerId? serverId}) => MediaItem(
serverId: serverId ?? ServerId('srv-jf'),
);
MediaItem _track(String id, {ServerId? serverId}) => MediaItem(
MediaItem _track(String id, {ServerId? serverId}) => testMediaItem(
id: id,
backend: MediaBackend.jellyfin,
kind: MediaKind.track,
@@ -146,7 +147,7 @@ void main() {
final ctx = await pumpContext(tester);
final launcher = JellyfinSequentialLauncher(context: ctx);
final orphan = MediaItem(
final orphan = testMediaItem(
id: 'col-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -173,7 +174,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col-99',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -273,7 +274,12 @@ void main() {
// container. If a future change reverts to fetchChildren the test
// fails because a Series row would leak into the queue.
final ctx = await pumpContext(tester);
final movie = MediaItem(id: 'movie-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
final movie = testMediaItem(
id: 'movie-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
serverId: 'srv-jf',
);
final ep1 = _ep('series-A-ep1');
final ep2 = _ep('series-A-ep2');
final fakeClient = _RecordingJellyfinClient(playableDescendantsResponse: [movie, ep1, ep2]);
@@ -287,7 +293,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col-mixed',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -323,7 +329,7 @@ void main() {
navigateForTesting: (_) async {},
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -360,7 +366,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col-start',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -394,7 +400,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -427,7 +433,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final folder = MediaItem(
final folder = testMediaItem(
id: 'folder-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.unknown,
@@ -465,7 +471,7 @@ void main() {
navigateForTesting: (_) async {},
);
final folder = MediaItem(
final folder = testMediaItem(
id: 'folder-shuffle',
backend: MediaBackend.jellyfin,
kind: MediaKind.unknown,
@@ -497,7 +503,7 @@ void main() {
},
);
final folder = MediaItem(
final folder = testMediaItem(
id: 'music-folder',
backend: MediaBackend.jellyfin,
kind: MediaKind.unknown,
@@ -515,7 +521,7 @@ void main() {
final ctx = await pumpContext(tester);
final launcher = JellyfinSequentialLauncher(context: ctx);
final movie = MediaItem(id: 'm1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
final movie = testMediaItem(id: 'm1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv-jf');
final result = await launcher.launchShuffledShow(metadata: movie, showLoadingIndicator: false);
@@ -527,7 +533,12 @@ void main() {
final ctx = await pumpContext(tester);
final launcher = JellyfinSequentialLauncher(context: ctx);
final season = MediaItem(id: 's1', backend: MediaBackend.jellyfin, kind: MediaKind.season, serverId: 'srv-jf');
final season = testMediaItem(
id: 's1',
backend: MediaBackend.jellyfin,
kind: MediaKind.season,
serverId: 'srv-jf',
);
final result = await launcher.launchShuffledShow(metadata: season, showLoadingIndicator: false);
@@ -539,7 +550,7 @@ void main() {
final ctx = await pumpContext(tester);
final launcher = JellyfinSequentialLauncher(context: ctx);
final orphan = MediaItem(id: 'show-orphan', backend: MediaBackend.jellyfin, kind: MediaKind.show);
final orphan = testMediaItem(id: 'show-orphan', backend: MediaBackend.jellyfin, kind: MediaKind.show);
final result = await launcher.launchShuffledShow(metadata: orphan, showLoadingIndicator: false);
@@ -563,7 +574,7 @@ void main() {
navigateForTesting: (m) async => navigated.add(m),
);
final show = MediaItem(
final show = testMediaItem(
id: 'show-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.show,
@@ -599,7 +610,7 @@ void main() {
navigateForTesting: (_) async {},
);
final season = MediaItem(
final season = testMediaItem(
id: 'season-2',
backend: MediaBackend.jellyfin,
kind: MediaKind.season,
@@ -628,7 +639,7 @@ void main() {
},
);
final show = MediaItem(
final show = testMediaItem(
id: 'show-empty',
backend: MediaBackend.jellyfin,
kind: MediaKind.show,
@@ -657,7 +668,7 @@ void main() {
},
);
final collection = MediaItem(
final collection = testMediaItem(
id: 'col-empty',
backend: MediaBackend.jellyfin,
kind: MediaKind.collection,
@@ -6,6 +6,7 @@ import 'package:plezy/services/local_playback_history.dart';
import 'package:plezy/services/settings_service.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
void main() {
setUp(() {
@@ -15,7 +16,7 @@ void main() {
});
test('recordPlayback writes item and series keys for an episode', () async {
final episode = MediaItem(
final episode = testMediaItem(
id: 'ep-1',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -32,7 +33,7 @@ void main() {
});
test('recordPlayback writes only the item key for a movie', () async {
final movie = MediaItem(
final movie = testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -47,7 +48,7 @@ void main() {
});
test('repeat writes for the same item within the rewrite window are skipped', () async {
final movie = MediaItem(
final movie = testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -67,7 +68,7 @@ void main() {
final settings = await SettingsService.getInstance();
await settings.write(SettingsService.localLastPlayedAt, {for (var i = 0; i < 400; i++) 'srv-1:old-$i': i + 1});
final movie = MediaItem(
final movie = testMediaItem(
id: 'fresh',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -19,10 +19,11 @@ import 'package:plezy/services/music/music_playback_service.dart';
import 'package:plezy/services/music/music_playback_service_impl.dart';
import 'package:plezy/services/music/music_source_resolver.dart';
import 'package:plezy/services/playback_coordinator.dart';
import '../../test_helpers/media_items.dart';
const _trackDuration = Duration(minutes: 3);
MediaItem _track(String id) => MediaItem(
MediaItem _track(String id) => testMediaItem(
id: id,
backend: MediaBackend.plex,
kind: MediaKind.track,
@@ -6,9 +6,10 @@ import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/music/music_playback_service.dart';
import 'package:plezy/services/music/music_queue_controller.dart';
import '../../test_helpers/media_items.dart';
MediaItem _track(String id) =>
MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: 'Track $id', serverId: 'srv');
testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: 'Track $id', serverId: 'srv');
List<String> _ids(List<MediaItem> items) => [for (final i in items) i.id];
@@ -21,6 +21,7 @@ import 'package:plezy/utils/watch_state_notifier.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// The actual sync-to-server path (`syncPendingItems`, `syncWatchStatesFromServer`,
@@ -93,7 +94,7 @@ class _RecordingMediaClient implements MediaServerClient {
@override
Future<MediaItem?> fetchItem(String id) async =>
MediaItem(id: id, backend: backend, kind: MediaKind.movie, serverId: serverId);
testMediaItem(id: id, backend: backend, kind: MediaKind.movie, serverId: serverId);
@override
Future<void> reportPlaybackStarted({
+2 -1
View File
@@ -5,6 +5,7 @@ import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/play_queue_launcher.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// `PlayQueueLauncher` is almost entirely network/UI glue:
@@ -82,7 +83,7 @@ void main() {
final launcher = PlexPlayQueueLauncher(context: capturedContext, client: _StubPlexClient());
final result = await launcher.launchShuffledShow(
// movie is not show / season.
metadata: MediaItem(id: 'rk1', backend: MediaBackend.plex, kind: MediaKind.movie),
metadata: testMediaItem(id: 'rk1', backend: MediaBackend.plex, kind: MediaKind.movie),
showLoadingIndicator: false,
);
@@ -23,6 +23,7 @@ import 'package:plezy/services/settings_service.dart';
import '../test_helpers/io_fakes.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -60,7 +61,7 @@ void main() {
await PlexApiCache.instance.put(ServerId('srv-1'), '/library/metadata/movie-1', _plexMetadataEnvelope());
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -89,7 +90,7 @@ void main() {
final client = _FailingPlaybackClient(serverId: ServerId('srv-1'));
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'track-1',
backend: MediaBackend.plex,
kind: MediaKind.track,
@@ -116,7 +117,7 @@ void main() {
final client = _FailingPlaybackClient(serverId: ServerId('srv-1'));
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -148,7 +149,7 @@ void main() {
);
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -181,7 +182,7 @@ void main() {
);
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -208,7 +209,7 @@ void main() {
);
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -238,7 +239,7 @@ void main() {
final client = _StreamingPlaybackClient(serverId: ServerId('srv-1'));
final result = await PlaybackInitializationService(client: client, database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -292,7 +293,7 @@ void main() {
);
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'item-1',
backend: MediaBackend.jellyfin,
kind: MediaKind.movie,
@@ -320,7 +321,7 @@ void main() {
await subtitleFile.writeAsString('1\n00:00:00,000 --> 00:00:01,000\nHello');
final result = await PlaybackInitializationService(database: db).getPlaybackData(
metadata: MediaItem(
metadata: testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -17,6 +17,7 @@ import 'package:plezy/services/plex_client.dart';
import 'package:plezy/utils/watch_state_notifier.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// `PlaybackProgressTracker` periodically samples the player's position and
@@ -291,13 +292,14 @@ class _StopMarksWatchedClient extends _FakePlexClient {
const Object _defaultServerId = Object();
MediaItem _meta({String ratingKey = '42', Object? serverId = _defaultServerId, String? type = 'movie'}) => MediaItem(
id: ratingKey,
backend: MediaBackend.plex,
kind: MediaKind.fromString(type),
title: 'Test Item',
serverId: identical(serverId, _defaultServerId) ? ServerId('srv') : serverId as ServerId?,
);
MediaItem _meta({String ratingKey = '42', Object? serverId = _defaultServerId, String? type = 'movie'}) =>
testMediaItem(
id: ratingKey,
backend: MediaBackend.plex,
kind: MediaKind.fromString(type),
title: 'Test Item',
serverId: identical(serverId, _defaultServerId) ? ServerId('srv') : serverId as ServerId?,
);
void main() {
setUp(resetSharedPreferencesForTest);
@@ -593,7 +595,7 @@ void main() {
);
final tracker = PlaybackProgressTracker(
client: client,
metadata: MediaItem(id: '42', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
metadata: testMediaItem(id: '42', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
player: player,
isOffline: false,
mediaInfo: mediaInfo,
+2 -1
View File
@@ -7,10 +7,11 @@ import 'package:plezy/models/transcode_quality_preset.dart';
import 'package:plezy/services/playback_context.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/services/playback_session.dart';
import '../test_helpers/media_items.dart';
PlaybackContext _context(PlaybackInitializationResult result) {
return PlaybackContext(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
result: result,
sourceKind: result.usesLocalMedia ? PlaybackSourceKind.localFile : PlaybackSourceKind.remoteDirect,
reportingMode: PlaybackReportingMode.online,
@@ -11,6 +11,7 @@ import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/playback_context.dart';
import 'package:plezy/services/playback_initialization_types.dart';
import 'package:plezy/services/playback_source_resolver.dart';
import '../test_helpers/media_items.dart';
class _PlaybackClient implements MediaServerClient {
_PlaybackClient({this.clientBackend = MediaBackend.plex, PlaybackInitializationResult? result})
@@ -56,7 +57,7 @@ void main() {
manager.debugRegisterClientForTesting(client, online: false);
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
selectedMediaIndex: 0,
offlineLibraryMode: false,
qualityPreset: TranscodeQualityPreset.original,
@@ -79,7 +80,7 @@ void main() {
manager.debugRegisterClientForTesting(client, online: true);
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'srv'),
selectedMediaIndex: 0,
offlineLibraryMode: false,
qualityPreset: TranscodeQualityPreset.original,
@@ -103,7 +104,7 @@ void main() {
manager.debugRegisterClientForTesting(client, online: true);
final context = await PlaybackSourceResolver(serverManager: manager, database: db).resolve(
metadata: MediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
metadata: testMediaItem(id: 'item-1', backend: MediaBackend.jellyfin, kind: MediaKind.movie, serverId: 'srv'),
selectedMediaIndex: 0,
offlineLibraryMode: false,
qualityPreset: TranscodeQualityPreset.original,
+45
View File
@@ -5,7 +5,11 @@ import 'package:drift/drift.dart' show Value;
import 'package:drift/native.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/media_backend.dart';
import 'package:plezy/services/api_cache.dart';
import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/plex_api_cache.dart';
import '../test_helpers/media_items.dart';
void main() {
late AppDatabase db;
@@ -54,6 +58,47 @@ void main() {
test('database getter exposes the underlying AppDatabase', () {
expect(identical(cache.database, db), isTrue);
});
test('registered cleanup ignores backend initialization order and preserves pinned rows', () async {
await cache.put(ServerId('srv'), '/volatile', {'value': 1});
await cache.put(ServerId('srv'), '/pinned', {'value': 2});
await cache.pin(ServerId('srv'), '/pinned');
// Register the other backend last; cleanup must not depend on whichever
// concrete singleton happened to initialize most recently.
JellyfinApiCache.initialize(db);
await ApiCache.clearRegisteredVolatile();
expect(await cache.get(ServerId('srv'), '/volatile'), isNull);
expect(await cache.get(ServerId('srv'), '/pinned'), {'value': 2});
});
test('registering a new database drops stale backend dispatch entries', () async {
final newDb = AppDatabase.forTesting(NativeDatabase.memory());
JellyfinApiCache.initialize(newDb);
expect(() => ApiCache.forBackend(MediaBackend.plex), throwsStateError);
expect(identical(ApiCache.forBackend(MediaBackend.jellyfin).database, newDb), isTrue);
await newDb.close();
});
});
group('shared row decoding', () {
test('drops malformed rows without discarding valid siblings', () {
final decoded = decodeCachedMediaRows(
['{"id":"first"}', 'not json', '[]', '{"id":"last"}'],
serializedData: (row) => row,
decode: (_, json) {
final id = json['id'] as String;
return MapEntry(id, testMediaItem(id: id));
},
);
expect(decoded.keys, ['first', 'last']);
expect(decoded['first']?.id, 'first');
expect(decoded['last']?.id, 'last');
});
});
// ============================================================
@@ -16,6 +16,7 @@ import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/media_items.dart';
void main() {
late AppDatabase db;
@@ -315,7 +316,7 @@ void main() {
final result = await client.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
metadata: testMediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
selectedMediaIndex: 0,
),
);
@@ -14,6 +14,7 @@ import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/plex_client.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/media_items.dart';
/// Regression coverage for the Plex transcode reporting bug: while
/// transcoding, the `/:/timeline` reports must carry the playback's
@@ -165,7 +166,7 @@ void main() {
final result = await client.getPlaybackInitialization(
PlaybackInitializationOptions(
metadata: MediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
metadata: testMediaItem(id: '42', backend: MediaBackend.plex, kind: MediaKind.movie, serverId: 'server-id'),
selectedMediaIndex: 0,
// Original preset stays on the direct-play branch (no transcode
// decision round-trip needed for this assertion).
+9 -8
View File
@@ -19,6 +19,7 @@ import 'package:plezy/services/sync_rule_executor.dart';
import '../test_helpers/backend_client_fixtures.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
JellyfinConnection _jellyfinConnection(String userId) => testJellyfinConnection(
machineId: 'jf-machine',
@@ -314,7 +315,7 @@ void main() {
manager.debugRegisterClientForTesting(client);
const ruleKey = 'profile-a|plex-machine:show-1';
final show = MediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
final show = testMediaItem(id: 'show-1', backend: MediaBackend.plex, kind: MediaKind.show, title: 'Show');
await db.insertSyncRule(
profileId: 'profile-a',
serverId: ServerId('plex-machine'),
@@ -357,7 +358,7 @@ void main() {
manager.debugRegisterClientForTesting(client);
const ruleKey = 'profile-a|plex-machine:collection-1';
final collection = MediaItem(
final collection = testMediaItem(
id: 'collection-1',
backend: MediaBackend.plex,
kind: MediaKind.collection,
@@ -406,10 +407,10 @@ void main() {
final items = [
_track('loose-track'),
MediaItem(id: 'album-1', backend: MediaBackend.plex, kind: MediaKind.album, title: 'Album'),
MediaItem(id: 'artist-1', backend: MediaBackend.plex, kind: MediaKind.artist, title: 'Artist'),
testMediaItem(id: 'album-1', backend: MediaBackend.plex, kind: MediaKind.album, title: 'Album'),
testMediaItem(id: 'artist-1', backend: MediaBackend.plex, kind: MediaKind.artist, title: 'Artist'),
// Still skipped: nested lists / unplayable kinds.
MediaItem(id: 'photo-1', backend: MediaBackend.plex, kind: MediaKind.photo, title: 'Photo'),
testMediaItem(id: 'photo-1', backend: MediaBackend.plex, kind: MediaKind.photo, title: 'Photo'),
];
final out = <MediaItem>[];
@@ -431,11 +432,11 @@ void main() {
}
MediaItem _track(String id, {bool played = false}) {
return MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: id, viewCount: played ? 1 : 0);
return testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.track, title: id, viewCount: played ? 1 : 0);
}
MediaItem _episode(String id, {required int parentIndex, required int index, String? originallyAvailableAt}) {
return MediaItem(
return testMediaItem(
id: id,
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -526,7 +527,7 @@ class _CollectionPagingClient implements MediaServerClient {
collectionPageCalls.add((start: start, size: size));
expect(collectionId, 'collection-1');
return LibraryPage(
items: [MediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie')],
items: [testMediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie')],
totalCount: 1,
offset: start ?? 0,
);
+2 -1
View File
@@ -11,6 +11,7 @@ import 'package:plezy/services/settings_service.dart';
import 'package:plezy/services/track_manager.dart';
import '../test_helpers/prefs.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// `TrackManager` orchestrates the player + Plex client + SettingsService
@@ -42,7 +43,7 @@ import '../test_helpers/prefs.dart';
// therefore gated on the same SettingsService dependency.
// - `resumeAfterSubtitleLoad` — schedules a real wall-clock fallback Timer.
MediaItem _meta({String id = 'rk1'}) => MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.movie);
MediaItem _meta({String id = 'rk1'}) => testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.movie);
/// Player that records calls and can be configured per-test.
class _FakePlayer with PlayerStreamControllersMixin implements Player {
@@ -8,6 +8,7 @@ import 'package:plezy/models/jellyfin/jellyfin_user_profile.dart';
import 'package:plezy/models/plex/plex_user_profile.dart';
import 'package:plezy/mpv/mpv.dart';
import 'package:plezy/services/track_selection_service.dart';
import '../test_helpers/media_items.dart';
// NOTE on coverage scope:
// `TrackSelectionService` is a large pure logic surface with one async
@@ -44,7 +45,7 @@ import 'package:plezy/services/track_selection_service.dart';
// ============================================================
MediaItem _meta({MediaBackend backend = MediaBackend.plex, String? audioLanguage, String? subtitleLanguage}) =>
MediaItem(
testMediaItem(
id: 'rk1',
backend: backend,
kind: MediaKind.movie,
@@ -5,6 +5,7 @@ import 'package:plezy/media/media_kind.dart';
import 'package:plezy/media/media_server_client.dart';
import 'package:plezy/models/trackers/anime_lists_mapping.dart';
import 'package:plezy/services/trackers/anime_episode_progress_resolver.dart';
import '../../test_helpers/media_items.dart';
class _FakeMediaServerClient implements MediaServerClient {
final Map<String, List<MediaItem>> childrenByParent;
@@ -33,7 +34,7 @@ class _FakeMediaServerClient implements MediaServerClient {
dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
MediaItem _season(int number, {int? watched, int? total}) => MediaItem(
MediaItem _season(int number, {int? watched, int? total}) => testMediaItem(
id: 'season-$number',
backend: MediaBackend.plex,
kind: MediaKind.season,
@@ -43,7 +44,7 @@ MediaItem _season(int number, {int? watched, int? total}) => MediaItem(
viewedLeafCount: watched,
);
MediaItem _episode({int season = 2, int number = 6, String showId = 'show-1', int? viewCount}) => MediaItem(
MediaItem _episode({int season = 2, int number = 6, String showId = 'show-1', int? viewCount}) => testMediaItem(
id: 'episode-$season-$number',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -18,6 +18,7 @@ import 'package:plezy/services/trackers/simkl/simkl_tracker.dart';
import 'package:plezy/services/trackers/tracker_coordinator.dart';
import 'package:plezy/services/trackers/tracker_session.dart';
import 'package:plezy/utils/external_ids.dart';
import '../../test_helpers/media_items.dart';
class _FakeMediaServerClient implements MediaServerClient {
@override
@@ -88,7 +89,7 @@ class _FakeAnimeListsLookup implements AnimeListsMappingLookup {
Future<Set<int>> lookupAnimeIdsForShow({int? tvdbId, int? tmdbId}) async => const <int>{};
}
MediaItem _season() => MediaItem(
MediaItem _season() => testMediaItem(
id: 'season-1',
backend: MediaBackend.plex,
kind: MediaKind.season,
@@ -99,7 +100,7 @@ MediaItem _season() => MediaItem(
parentId: 'show-1',
);
MediaItem _episode(int number, {int season = 1}) => MediaItem(
MediaItem _episode(int number, {int season = 1}) => testMediaItem(
id: 'episode-$season-$number',
backend: MediaBackend.plex,
kind: MediaKind.episode,
@@ -110,7 +111,7 @@ MediaItem _episode(int number, {int season = 1}) => MediaItem(
index: number,
);
MediaItem _show() => MediaItem(
MediaItem _show() => testMediaItem(
id: 'show-1',
backend: MediaBackend.plex,
kind: MediaKind.show,
@@ -119,7 +120,7 @@ MediaItem _show() => MediaItem(
libraryId: 'lib-1',
);
MediaItem _movie() => MediaItem(
MediaItem _movie() => testMediaItem(
id: 'movie-1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
@@ -10,6 +10,7 @@ import 'package:plezy/services/trackers/anime_lists_mapping_store.dart';
import 'package:plezy/services/trackers/fribb_mapping_store.dart';
import 'package:plezy/services/trackers/tracker_id_resolver.dart';
import 'package:plezy/utils/external_ids.dart';
import '../../test_helpers/media_items.dart';
class _FakeMediaServerClient implements MediaServerClient {
final Map<String, ExternalIds> externalIdsByItem;
@@ -94,7 +95,7 @@ class _FakeAnimeListsLookup implements AnimeListsMappingLookup {
Future<Set<int>> lookupAnimeIdsForShow({int? tvdbId, int? tmdbId}) async => const <int>{};
}
MediaItem _episode({int season = 23, int number = 6}) => MediaItem(
MediaItem _episode({int season = 23, int number = 6}) => testMediaItem(
id: 'episode-$season-$number',
backend: MediaBackend.plex,
kind: MediaKind.episode,
+2 -1
View File
@@ -6,6 +6,7 @@ import 'package:plezy/media/media_item.dart';
import 'package:plezy/media/media_kind.dart';
import 'package:plezy/services/watch_state_resolver.dart';
import 'package:plezy/utils/watch_state_notifier.dart';
import '../test_helpers/media_items.dart';
OfflineWatchProgressItem _action({
required String actionType,
@@ -86,7 +87,7 @@ void main() {
test('applying a watched snapshot patches container leaf counts so isWatched flips', () {
const snapshot = WatchStateSnapshot(isWatched: true, hasViewOffsetMs: true, viewOffsetMs: 0);
final season = MediaItem(
final season = testMediaItem(
id: 'season-1',
backend: MediaBackend.plex,
kind: MediaKind.season,