refactor(features): consolidate shared feature primitives
This commit is contained in:
@@ -5,9 +5,10 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_item_types.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/utils/content_utils.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
MediaItem _episode({int? viewOffsetMs, int? durationMs, int? viewCount, int? leafCount, int? viewedLeafCount}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: '1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -20,7 +21,7 @@ MediaItem _episode({int? viewOffsetMs, int? durationMs, int? viewCount, int? lea
|
||||
}
|
||||
|
||||
MediaItem _movie({int? viewCount}) {
|
||||
return MediaItem(id: '1', backend: MediaBackend.plex, kind: MediaKind.movie, viewCount: viewCount);
|
||||
return testMediaItem(id: '1', backend: MediaBackend.plex, kind: MediaKind.movie, viewCount: viewCount);
|
||||
}
|
||||
|
||||
void main() {
|
||||
@@ -58,7 +59,7 @@ void main() {
|
||||
group('MediaItemTypes.shouldHideSpoiler', () {
|
||||
test('false for non-episodes', () {
|
||||
expect(_movie().shouldHideSpoiler, isFalse);
|
||||
final show = MediaItem(id: '1', backend: MediaBackend.plex, kind: MediaKind.show);
|
||||
final show = testMediaItem(id: '1', backend: MediaBackend.plex, kind: MediaKind.show);
|
||||
expect(show.shouldHideSpoiler, isFalse);
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/database/app_database.dart';
|
||||
import 'package:plezy/models/download_models.dart';
|
||||
import 'package:plezy/utils/downloaded_version_match.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
DownloadedMediaItem _row({int mediaIndex = 0, String? mediaSourceId}) {
|
||||
return DownloadedMediaItem(
|
||||
return DownloadedtestMediaItem(
|
||||
id: 1,
|
||||
serverId: 'srv',
|
||||
ratingKey: 'movie-1',
|
||||
|
||||
@@ -9,8 +9,9 @@ import 'package:plezy/media/media_server_client.dart';
|
||||
import 'package:plezy/media/media_version.dart';
|
||||
import 'package:plezy/utils/download_version_utils.dart';
|
||||
import 'package:plezy/media/episode_collection.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
MediaItem _season(String id, {int index = 1, int? leafCount, int? viewedLeafCount}) => MediaItem(
|
||||
MediaItem _season(String id, {int index = 1, int? leafCount, int? viewedLeafCount}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.season,
|
||||
@@ -31,7 +32,7 @@ MediaItem _episode(
|
||||
int? viewOffsetMs,
|
||||
int? durationMs,
|
||||
String? originallyAvailableAt,
|
||||
}) => MediaItem(
|
||||
}) => testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -47,7 +48,7 @@ MediaItem _episode(
|
||||
originallyAvailableAt: originallyAvailableAt,
|
||||
);
|
||||
|
||||
MediaItem _clip(String id) => MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.clip, title: 'Clip');
|
||||
MediaItem _clip(String id) => testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.clip, title: 'Clip');
|
||||
|
||||
class _RecordingClient implements MediaServerClient {
|
||||
_RecordingClient({this.childrenByParent = const {}, this.childrenPageByParent = const {}, this.itemsById = const {}});
|
||||
@@ -111,7 +112,7 @@ class _LeavesClient implements MediaServerClient {
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('collectEpisodesForShow drops Specials when includeSpecials is false', () async {
|
||||
test('collectEpisodes drops Specials when includeSpecials is false', () async {
|
||||
final client = _LeavesClient([
|
||||
_episode('s1e1', parentIndex: 1, index: 1, originallyAvailableAt: '2022-10-05'),
|
||||
_episode('s0e1', parentIndex: 0, index: 1, originallyAvailableAt: '2022-10-27'),
|
||||
@@ -119,12 +120,12 @@ void main() {
|
||||
]);
|
||||
|
||||
final withoutSpecials = <MediaItem>[];
|
||||
await collectEpisodesForShow(client, 'show-1', unwatchedOnly: false, out: withoutSpecials, includeSpecials: false);
|
||||
await collectEpisodes(client, 'show-1', unwatchedOnly: false, out: withoutSpecials, includeSpecials: false);
|
||||
expect(withoutSpecials.map((e) => e.id), ['s1e1', 's1e2']);
|
||||
|
||||
// Default keeps Specials, interleaved into aired order.
|
||||
final withSpecials = <MediaItem>[];
|
||||
await collectEpisodesForShow(client, 'show-1', unwatchedOnly: false, out: withSpecials);
|
||||
await collectEpisodes(client, 'show-1', unwatchedOnly: false, out: withSpecials);
|
||||
expect(withSpecials.map((e) => e.id), ['s1e1', 's0e1', 's1e2']);
|
||||
});
|
||||
|
||||
@@ -317,7 +318,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('fetchSeasonEpisodePage normalizes show and season identity', () async {
|
||||
final show = MediaItem(
|
||||
final show = testMediaItem(
|
||||
id: 'show-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.show,
|
||||
@@ -350,7 +351,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('fetchSeasonEpisodePage uses season episode paging when available', () async {
|
||||
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');
|
||||
final season = _season('season-1');
|
||||
final row = _episode('episode-1');
|
||||
final client = _SeasonPagingRecordingClient(
|
||||
@@ -367,7 +368,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('normalizeSeasonEpisodes ignores non-episode rows', () {
|
||||
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');
|
||||
final season = _season('season-1');
|
||||
|
||||
final normalized = normalizeSeasonEpisodes([_clip('extra-1'), _episode('episode-1')], show: show, season: season);
|
||||
@@ -395,7 +396,7 @@ void main() {
|
||||
|
||||
test('fetchRepresentativeVersions keeps full season lookup but pages selected season episodes', () async {
|
||||
final versions = [const MediaVersion(id: '1080', videoResolution: '1080')];
|
||||
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');
|
||||
final special = _season('specials', index: 0);
|
||||
final firstRegularSeason = _season('season-1');
|
||||
final episodeRow = _episode('episode-1');
|
||||
|
||||
@@ -227,4 +227,31 @@ void main() {
|
||||
expect(flexibleCsvStringList(<dynamic>[]), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('flexible JSON objects', () {
|
||||
String parseId(Map<String, dynamic> json) => json['id'] as String;
|
||||
|
||||
test('list parser keeps valid siblings around malformed entries', () {
|
||||
final parsed = parseFlexibleJsonList([
|
||||
{'id': 'first'},
|
||||
{'id': 2},
|
||||
'not-a-map',
|
||||
{'id': 'last'},
|
||||
], parseId);
|
||||
|
||||
expect(parsed, ['first', 'last']);
|
||||
});
|
||||
|
||||
test('object parser finds the first map and contains parse failures', () {
|
||||
expect(
|
||||
parseFlexibleJsonObject([
|
||||
'not-a-map',
|
||||
{'id': 'value'},
|
||||
], parseId),
|
||||
'value',
|
||||
);
|
||||
expect(parseFlexibleJsonObject({'id': 2}, parseId), isNull);
|
||||
expect(parseFlexibleJsonObject(null, parseId), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/media/media_library.dart';
|
||||
import 'package:plezy/utils/media_hub_ordering.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
const Object _defaultServerId = Object();
|
||||
|
||||
@@ -20,7 +21,7 @@ MediaLibrary _library(String id, {ServerId? serverId}) {
|
||||
}
|
||||
|
||||
MediaItem _item(String id, {String? libraryId, Object? serverId = _defaultServerId}) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: id,
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.movie,
|
||||
|
||||
@@ -4,10 +4,11 @@ import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/utils/media_navigation_helper.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
test('episode detail target opens parent show and focuses season episode', () {
|
||||
final episode = MediaItem(
|
||||
final episode = testMediaItem(
|
||||
id: 'episode-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -29,7 +30,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('season detail target opens parent show and focuses season', () {
|
||||
final season = MediaItem(
|
||||
final season = testMediaItem(
|
||||
id: 'season-3',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.season,
|
||||
@@ -50,7 +51,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('movie detail target keeps the movie itself', () {
|
||||
final movie = MediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie');
|
||||
final movie = testMediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie');
|
||||
|
||||
final target = mediaDetailNavigationTargetFor(movie);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:plezy/media/media_version.dart';
|
||||
import 'package:plezy/services/jellyfin_mappers.dart';
|
||||
import 'package:plezy/services/plex_mappers.dart';
|
||||
import 'package:plezy/utils/media_quality_labels.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
group('buildMediaQualityLabels', () {
|
||||
@@ -216,7 +217,7 @@ void main() {
|
||||
}
|
||||
|
||||
MediaItem _episodeWithVersion(MediaVersion? version) {
|
||||
return MediaItem(
|
||||
return testMediaItem(
|
||||
id: 'episode-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:plezy/media/media_backend.dart';
|
||||
import 'package:plezy/media/media_item.dart';
|
||||
import 'package:plezy/media/media_kind.dart';
|
||||
import 'package:plezy/utils/plex_season_display.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
group('shouldShowPlexEpisodesDirectly', () {
|
||||
@@ -57,9 +58,9 @@ void main() {
|
||||
}
|
||||
|
||||
MediaItem _show({Map<String, Object?>? raw}) {
|
||||
return MediaItem(id: 'show', backend: MediaBackend.plex, kind: MediaKind.show, raw: raw);
|
||||
return testMediaItem(id: 'show', backend: MediaBackend.plex, kind: MediaKind.show, raw: raw);
|
||||
}
|
||||
|
||||
MediaItem _season(String id) {
|
||||
return MediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.season);
|
||||
return testMediaItem(id: id, backend: MediaBackend.plex, kind: MediaKind.season);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/media/media_backend.dart';
|
||||
import 'package:plezy/media/media_item.dart';
|
||||
@@ -9,11 +10,21 @@ import 'package:plezy/services/settings_service.dart';
|
||||
import 'package:plezy/utils/video_player_navigation.dart';
|
||||
|
||||
import '../test_helpers/prefs.dart';
|
||||
import '../test_helpers/media_items.dart';
|
||||
|
||||
void main() {
|
||||
test('VOD and Live TV route contract is opaque, named, and transition-free', () {
|
||||
final route = buildVideoPlayerRoute(builder: (_) => const SizedBox());
|
||||
|
||||
expect(route.settings.name, kVideoPlayerRouteName);
|
||||
expect(route.opaque, isTrue);
|
||||
expect(route.transitionDuration, Duration.zero);
|
||||
expect(route.reverseTransitionDuration, Duration.zero);
|
||||
});
|
||||
|
||||
test('in-flight video player navigation rejects duplicate requests', () {
|
||||
final guard = VideoPlayerNavigationInFlightGuard();
|
||||
final item = MediaItem(
|
||||
final item = testMediaItem(
|
||||
id: 'episode_1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -48,7 +59,7 @@ void main() {
|
||||
MediaVersion(id: '102', videoResolution: '4k', videoCodec: 'hevc', container: 'mkv'),
|
||||
];
|
||||
|
||||
final episode = MediaItem(
|
||||
final episode = testMediaItem(
|
||||
id: 'ep-1',
|
||||
backend: MediaBackend.plex,
|
||||
kind: MediaKind.episode,
|
||||
@@ -122,7 +133,7 @@ void main() {
|
||||
);
|
||||
SettingsService.resetForTesting();
|
||||
|
||||
final bare = MediaItem(
|
||||
final bare = testMediaItem(
|
||||
id: 'ep-2',
|
||||
backend: MediaBackend.jellyfin,
|
||||
kind: MediaKind.episode,
|
||||
|
||||
Reference in New Issue
Block a user