test: remove redundant coverage and shorten timers

This commit is contained in:
edde746
2026-07-13 02:15:03 +02:00
parent c4e9fa1650
commit e6e7d8cdfd
63 changed files with 90 additions and 1760 deletions
@@ -114,16 +114,14 @@ void main() {
return cached!['UserData'] as Map<String, dynamic>;
}
test('mutates every per-user row for the same item', () async {
// Jellyfin caches one row per userId — both must flip, otherwise a
// profile switch surfaces the other user's stale row (audit D-cluster).
test('skips ambiguous bare scope when multiple users cache the same item', () async {
await JellyfinApiCache.instance.put(serverId, '/Users/user-a/Items/item-1', dto());
await JellyfinApiCache.instance.put(serverId, '/Users/user-b/Items/item-1', dto());
await JellyfinApiCache.instance.applyWatchState(serverId: serverId, itemId: 'item-1', isWatched: true);
expect((await readBack('user-a'))['Played'], isTrue);
expect((await readBack('user-b'))['Played'], isTrue);
expect((await readBack('user-a'))['Played'], isFalse);
expect((await readBack('user-b'))['Played'], isFalse);
});
test('converts viewOffsetMs to 100-ns ticks', () async {
@@ -33,22 +33,16 @@ void main() {
}
});
// ============================================================
// Singleton + reset
// ============================================================
group('singleton', () {
test('instance returns same object across calls', () {
final a = DownloadStorageService.instance;
final b = DownloadStorageService.instance;
expect(identical(a, b), isTrue);
});
test('resetForTesting yields a fresh instance', () {
group('singleton lifecycle', () {
test('reacquiring the instance preserves initialized state', () async {
final settings = await SettingsService.getInstance();
final first = DownloadStorageService.instance;
DownloadStorageService.resetForTesting();
await first.initialize(settings);
final second = DownloadStorageService.instance;
expect(identical(first, second), isFalse);
expect(identical(first, second), isTrue);
expect(second.artworkDirectoryPath, isNotNull);
expect(second.artworkDirectoryPath, first.artworkDirectoryPath);
});
});
@@ -275,15 +269,6 @@ void main() {
expect(await dss.toRelativePath(uri), uri);
});
test('returns the input unchanged when not under the base dir', () async {
final settings = await SettingsService.getInstance();
final dss = DownloadStorageService.instance;
await dss.initialize(settings);
const foreign = '/some/other/place/file.mkv';
expect(await dss.toRelativePath(foreign), foreign);
});
test('toAbsolutePath joins relative paths against the base dir', () async {
final settings = await SettingsService.getInstance();
final dss = DownloadStorageService.instance;
@@ -104,44 +104,6 @@ class _ProbeWidgetState extends State<_ProbeWidget> {
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
// ===========================================================
// AdjacentEpisodes data class
// ===========================================================
group('AdjacentEpisodes', () {
test('default constructor reports no neighbours', () {
final ae = AdjacentEpisodes();
expect(ae.next, isNull);
expect(ae.previous, isNull);
expect(ae.hasNext, isFalse);
expect(ae.hasPrevious, isFalse);
});
test('next/previous flags reflect non-null fields', () {
final ae = AdjacentEpisodes(next: _meta('n'), previous: _meta('p'));
expect(ae.hasNext, isTrue);
expect(ae.hasPrevious, isTrue);
expect(ae.next!.id, 'n');
expect(ae.previous!.id, 'p');
});
test('only-next variant', () {
final ae = AdjacentEpisodes(next: _meta('n'));
expect(ae.hasNext, isTrue);
expect(ae.hasPrevious, isFalse);
});
test('only-previous variant', () {
final ae = AdjacentEpisodes(previous: _meta('p'));
expect(ae.hasNext, isFalse);
expect(ae.hasPrevious, isTrue);
});
});
// ===========================================================
// loadAdjacentEpisodes: short-circuit without an active queue
// ===========================================================
group('loadAdjacentEpisodes', () {
testWidgets('returns empty AdjacentEpisodes when no play queue is active', (tester) async {
// Bare provider — no setPlaybackFromPlayQueue() call → isQueueActive = false.
-30
View File
@@ -147,34 +147,4 @@ void main() {
expect(out.audioTracks, hasLength(1));
});
});
group('cross-backend equivalence', () {
test('both readers produce parallel track structures from analogous JSON', () {
const plexReader = PlexFileInfoStreamReader();
const jfReader = JellyfinFileInfoStreamReader();
final plexStreams = [
{'streamType': 1, 'id': 1, 'frameRate': 24.0},
{'streamType': 2, 'id': 2, 'codec': 'aac', 'language': 'English', 'channels': 2, 'selected': true},
{'streamType': 3, 'id': 3, 'codec': 'srt', 'language': 'English', 'selected': false, 'forced': false},
];
final jfStreams = [
{'Type': 'Video', 'Index': 0, 'RealFrameRate': 24.0},
{'Type': 'Audio', 'Index': 1, 'Codec': 'aac', 'Language': 'eng', 'Channels': 2, 'IsDefault': true},
{'Type': 'Subtitle', 'Index': 2, 'Codec': 'srt', 'Language': 'eng', 'IsDefault': false, 'IsForced': false},
];
final plex = walkStreams(plexStreams, plexReader);
final jf = walkStreams(jfStreams, jfReader);
expect(plex.audioTracks, hasLength(1));
expect(jf.audioTracks, hasLength(1));
expect(plex.subtitleTracks, hasLength(1));
expect(jf.subtitleTracks, hasLength(1));
expect(plex.videoStream?['frameRate'], jf.videoStream?['RealFrameRate']);
expect(plex.audioTracks.first.codec, jf.audioTracks.first.codec);
expect(plex.audioTracks.first.channels, jf.audioTracks.first.channels);
expect(plex.audioTracks.first.selected, jf.audioTracks.first.selected);
});
});
}
+2 -47
View File
@@ -15,9 +15,9 @@ import '../test_helpers/media_items.dart';
// - then calls [navigateToVideoPlayer] (Navigator + DownloadProvider +
// SettingsService singleton + Provider).
//
// Without re-implementing that entire dependency tree, the only meaningful
// Without re-implementing that entire dependency tree, the meaningful
// unit-testable surface is:
// - The `PlayQueueResult` sealed hierarchy (constructor + identity).
// - `PlayQueueError` preserves the underlying failure.
// - `launchShuffledShow` short-circuits BEFORE any network call when the
// metadata is not a show or season — that's a pure pre-flight branch.
// - `launchFromCollectionOrPlaylist` short-circuits when the input is
@@ -39,20 +39,6 @@ void main() {
// ============================================================
group('PlayQueueResult', () {
test('PlayQueueSuccess is a const, identity-comparable singleton', () {
const a = PlayQueueSuccess();
const b = PlayQueueSuccess();
expect(identical(a, b), isTrue);
expect(a, isA<PlayQueueResult>());
});
test('PlayQueueEmpty is a const, identity-comparable singleton', () {
const a = PlayQueueEmpty();
const b = PlayQueueEmpty();
expect(identical(a, b), isTrue);
expect(a, isA<PlayQueueResult>());
});
test('PlayQueueError carries the wrapped error', () {
final error = StateError('boom');
final result = PlayQueueError(error);
@@ -114,35 +100,4 @@ void main() {
expect(error.toString(), contains('collection or playlist'));
});
});
// ============================================================
// Constructor
// ============================================================
group('constructor', () {
testWidgets('stores all wired arguments', (tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(
Builder(
builder: (context) {
capturedContext = context;
return const SizedBox.shrink();
},
),
);
final client = _StubPlexClient();
final launcher = PlexPlayQueueLauncher(
context: capturedContext,
client: client,
serverId: 'srv-A',
serverName: 'Plex',
);
expect(launcher.context, capturedContext);
expect(identical(launcher.client, client), isTrue);
expect(launcher.serverId, 'srv-A');
expect(launcher.serverName, 'Plex');
});
});
}
@@ -322,18 +322,6 @@ void main() {
throwsA(isA<AssertionError>()),
);
});
test('valid online construction succeeds', () {
final tracker = PlaybackProgressTracker(
client: _FakePlexClient(),
metadata: _meta(),
player: _FakePlayer(),
isOffline: false,
);
addTearDown(tracker.dispose);
// No assertion — the constructor returned cleanly.
expect(tracker, isNotNull);
});
});
// ============================================================
-24
View File
@@ -97,28 +97,4 @@ void main() {
expect(session.mediaSourceId, 'downloaded');
});
});
test('forwarding getters mirror the resolver output', () {
final result = PlaybackInitializationResult(
availableVersions: [MediaVersion(id: 'v0')],
videoUrl: 'u',
isTranscoding: true,
playSessionId: 'psid',
playMethod: 'Transcode',
activeAudioStreamId: 7,
);
final session = PlaybackSession.fromContext(
_context(result),
requestedQualityPreset: TranscodeQualityPreset.original,
);
expect(session.isTranscoding, isTrue);
expect(session.isOffline, isFalse);
expect(session.playSessionId, 'psid');
expect(session.playMethod, 'Transcode');
expect(session.audioStreamId, 7);
expect(session.availableVersions, hasLength(1));
expect(session.streamHeaders, containsPair('X-Test', 'token'));
expect(session.metadata.id, 'item-1');
});
}
-4
View File
@@ -55,10 +55,6 @@ void main() {
await newDb.close();
});
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});
+12 -40
View File
@@ -274,40 +274,20 @@ void main() {
return client;
}
test('trending drops person results and keeps native mediaType', () async {
test('popular movies coerces missing mediaType to movie', () async {
final client = clientWith(
MockClient(
(request) async => _json({
'page': 1,
'totalPages': 2,
'results': [
{'id': 1, 'mediaType': 'movie', 'title': 'Blade Runner', 'releaseDate': '1982-06-25'},
{'id': 2, 'mediaType': 'person', 'name': 'Harrison Ford'},
{'id': 3, 'mediaType': 'tv', 'name': 'Severance', 'firstAirDate': '2022-02-18'},
],
}),
),
);
final page = await client.getTrending();
expect(page.items.map((m) => m.displayTitle), ['Blade Runner', 'Severance']);
expect(page.items.first.isMovie, isTrue);
expect(page.items.last.isMovie, isFalse);
expect(page.items.first.year, 1982);
expect(page.hasMore, isTrue);
});
test('single-type discover endpoints coerce the missing mediaType', () async {
final client = clientWith(
MockClient(
(request) async => _json({
MockClient((request) async {
expect(request.url.path, '/api/v1/discover/movies');
return _json({
'page': 1,
'totalPages': 1,
'results': [
{'id': 4, 'title': 'Dune', 'releaseDate': '2021-09-15'},
],
}),
),
});
}),
);
final page = await client.getPopularMovies();
expect(page.items.single.isMovie, isTrue);
expect(page.hasMore, isFalse);
@@ -359,24 +339,16 @@ void main() {
});
group('SeerrPage', () {
test('parses both the TMDB and the pageInfo pagination shapes', () {
final tmdbShape = SeerrPage<int>.fromJson({
'page': 1,
'totalPages': 3,
'results': [
{'id': 1},
],
}, (item) => item['id'] as int);
expect(tmdbShape.hasMore, isTrue);
final pageInfoShape = SeerrPage<int>.fromJson({
test('parses the pageInfo pagination shape', () {
final page = SeerrPage<int>.fromJson({
'pageInfo': {'page': 2, 'pages': 2},
'results': [
{'id': 1},
],
}, (item) => item['id'] as int);
expect(pageInfoShape.hasMore, isFalse);
expect(pageInfoShape.items, [1]);
expect(page.hasMore, isFalse);
expect(page.items, [1]);
});
});
+1 -56
View File
@@ -19,8 +19,7 @@ import '../test_helpers/media_items.dart';
// initialized SettingsService.
//
// Coverage:
// - Constructor wiring (mutable fields are settable, default values).
// - `cacheExternalSubtitles` / `lastExternalSubtitles` round-trip.
// - `cacheExternalSubtitles` / `lastExternalSubtitles` replacement behavior.
// - `addExternalSubtitles` invokes the player's addSubtitleTrack for each
// entry with a non-null URI, preserves order, and silently swallows errors
// thrown by the player.
@@ -28,8 +27,6 @@ import '../test_helpers/media_items.dart';
// fewer than 2 real tracks (early-return paths).
// - `applyTrackSelectionWhenReady` waits for subtitle tracks when server
// metadata says they exist.
// - `onPlaybackRestart` is a no-op when not waiting for external subs.
// - `onSecondarySubtitleTrackChanged` is a documented no-op.
// - `dispose` is idempotent (timers/subscriptions cleared).
//
// What's NOT covered:
@@ -150,49 +147,6 @@ void main() {
// could leak across tests — reset to be safe.
setUp(resetSharedPreferencesForTest);
// ============================================================
// Construction
// ============================================================
group('constructor', () {
test('initialises mutable fields with the provided values', () {
final player = _FakePlayer();
final mgr = TrackManager(
player: player,
isActive: () => true,
persistTrackPreference: _noopPersister,
getProfileSettings: () => null,
waitForProfileSettings: () async {},
metadata: _meta(),
preferredAudioTrack: const AudioTrack(id: 'a-1', language: 'eng'),
preferredSubtitleTrack: const SubtitleTrack(id: 's-1', language: 'eng'),
preferredSecondarySubtitleTrack: const SubtitleTrack(id: 's-2', language: 'fre'),
);
addTearDown(mgr.dispose);
expect(mgr.preferredAudioTrack?.id, 'a-1');
expect(mgr.preferredSubtitleTrack?.id, 's-1');
expect(mgr.preferredSecondarySubtitleTrack?.id, 's-2');
expect(mgr.metadata.id, 'rk1');
expect(mgr.waitingForExternalSubsTrackSelection, isFalse);
expect(mgr.lastExternalSubtitles, isEmpty);
expect(mgr.mediaInfo, isNull);
});
test('mutable fields can be reassigned (episode-navigation pattern)', () {
final mgr = _make(player: _FakePlayer());
addTearDown(mgr.dispose);
mgr.metadata = _meta(id: 'next');
mgr.preferredAudioTrack = const AudioTrack(id: 'a2', language: 'fre');
mgr.waitingForExternalSubsTrackSelection = true;
expect(mgr.metadata.id, 'next');
expect(mgr.preferredAudioTrack?.id, 'a2');
expect(mgr.waitingForExternalSubsTrackSelection, isTrue);
});
});
// ============================================================
// External subtitle cache
// ============================================================
@@ -506,15 +460,6 @@ void main() {
});
});
group('onSecondarySubtitleTrackChanged', () {
test('is a documented no-op', () {
final mgr = _make(player: _FakePlayer());
addTearDown(mgr.dispose);
// Just verify it returns normally; nothing else to assert.
expect(() => mgr.onSecondarySubtitleTrackChanged(const SubtitleTrack(id: '1')), returnsNormally);
});
});
// ============================================================
// onSubtitleTrackChanged — same-language stream mapping (#1443)
// ============================================================
@@ -19,13 +19,6 @@ void main() {
});
group('tracker session json codec', () {
test('round-trips through provided factory', () {
final encoded = encodeTrackerSessionJson({'access_token': 'abc', 'created_at': 123});
final decoded = decodeTrackerSessionJson(encoded, (json) => json);
expect(decoded, {'access_token': 'abc', 'created_at': 123});
});
test('round-trips Trakt sessions with snake-case keys and default scope', () {
const session = TrackerSession(
accessToken: 'trakt-at',
@@ -59,17 +52,6 @@ void main() {
expect(decoded.createdAt, 1000);
});
test('round-trips AniList sessions through shared encode mixin', () {
const session = TrackerSession(accessToken: 'anilist-at', expiresAt: 2000, username: 'alice', createdAt: 1000);
final decoded = TrackerSession.decode(session.encode());
expect(decoded.accessToken, 'anilist-at');
expect(decoded.expiresAt, 2000);
expect(decoded.username, 'alice');
expect(decoded.createdAt, 1000);
});
test('round-trips MAL sessions through shared encode mixin', () {
const session = TrackerSession(
accessToken: 'mal-at',
@@ -88,16 +70,6 @@ void main() {
expect(decoded.createdAt, 1000);
});
test('round-trips Simkl sessions through shared encode mixin', () {
const session = TrackerSession(accessToken: 'simkl-at', username: 'carol', createdAt: 1000);
final decoded = TrackerSession.decode(session.encode());
expect(decoded.accessToken, 'simkl-at');
expect(decoded.username, 'carol');
expect(decoded.createdAt, 1000);
});
test('builds Trakt token sessions with default scope', () {
final session = TrackerSession.fromTokenResponse(TrackerService.trakt, {
'access_token': 'trakt-at',