Files
plezy/test/screens/libraries/library_collections_tab_test.dart
edde746 61344f7862 test: extract shared fixtures and scaffolds
Collapse duplicated setup across the suite into six shared helpers under
test/test_helpers/ and rewrite the 28 suites that were open-coding it:

  http_fixtures.dart         jsonResponse() for http.Response JSON stubs
  library_tab_scaffold.dart  pumps library tabs under their required ancestors
  multi_server_fixtures.dart MultiServerProvider wiring for widget tests
  playback_report_fakes.dart PlaybackReportCall + fake report sinks
  profile_stack.dart         production-shaped profile dependency graph
  theme.dart                 testMonoTokens for fast-settling widget tests

Net -1245 lines with no change in coverage or assertions.
2026-07-26 06:09:47 +02:00

193 lines
6.8 KiB
Dart

import 'dart:convert';
import 'package:drift/native.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;
import 'package:http/testing.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/media/ids.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/media/media_library.dart';
import 'package:plezy/media/media_server_client.dart';
import 'package:plezy/models/plex/plex_config.dart';
import 'package:plezy/providers/multi_server_provider.dart';
import 'package:plezy/screens/libraries/tabs/library_collections_tab.dart';
import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/jellyfin_client.dart';
import 'package:plezy/services/multi_server_manager.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/utils/platform_detector.dart';
import 'package:plezy/widgets/card_inflation_budget.dart';
import 'package:plezy/widgets/focusable_media_card.dart';
import 'package:plezy/widgets/media_card_sliver_layout.dart';
import '../../test_helpers/backend_client_fixtures.dart';
import '../../test_helpers/library_tab_scaffold.dart';
import '../../test_helpers/multi_server_fixtures.dart';
import '../../test_helpers/prefs.dart';
final _serverId = ServerId('collection-server');
final _jellyfinServerId = ServerId('jellyfin-collection-server');
final _musicLibrary = MediaLibrary(
id: 'music',
backend: MediaBackend.plex,
title: 'Music',
kind: MediaKind.artist,
serverId: _serverId,
);
final _jellyfinMusicLibrary = MediaLibrary(
id: 'music-library',
backend: MediaBackend.jellyfin,
title: 'Music',
kind: MediaKind.artist,
serverId: _jellyfinServerId,
);
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
setUp(() async {
resetSharedPreferencesForTest();
SettingsService.resetForTesting();
CardInflationBudget.reset();
TvDetectionService.debugSetAppleTVOverride(false);
await SettingsService.getInstance();
});
tearDown(() => TvDetectionService.debugSetAppleTVOverride(null));
testWidgets('music library collections use square grid geometry and square cards', (tester) async {
final harness = _CollectionHarness.plex();
addTearDown(harness.dispose);
TvDetectionService.debugSetAppleTVOverride(true);
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
await _pumpTab(tester, harness: harness, library: _musicLibrary);
final layout = tester.widget<MediaCardSliverLayout>(find.byType(MediaCardSliverLayout));
expect(layout.shape, CardShape.square);
expect(layout.fullBleedImage, isFalse);
expect(tester.widget<FocusableMediaCard>(find.byType(FocusableMediaCard)).cardShapeOverride, CardShape.square);
});
testWidgets('Jellyfin video collections keep poster geometry when opened from a music library', (tester) async {
final harness = _CollectionHarness.jellyfin();
addTearDown(harness.dispose);
TvDetectionService.debugSetAppleTVOverride(true);
await SettingsService.instance.write(SettingsService.tvFullCardLayout, true);
await _pumpTab(tester, harness: harness, library: _jellyfinMusicLibrary);
final layout = tester.widget<MediaCardSliverLayout>(find.byType(MediaCardSliverLayout));
expect(layout.shape, isNull);
expect(layout.fullBleedImage, isTrue);
expect(tester.widget<FocusableMediaCard>(find.byType(FocusableMediaCard)).cardShapeOverride, isNull);
});
}
Future<void> _pumpTab(WidgetTester tester, {required _CollectionHarness harness, required MediaLibrary library}) async {
await pumpLibraryTab(
tester,
provider: harness.provider,
tab: LibraryCollectionsTab(library: library, suppressAutoFocus: true, onBack: () {}),
size: const Size(800, 600),
);
await tester.pumpAndSettle();
}
class _CollectionHarness {
final AppDatabase database;
late final MultiServerManager manager;
late final MultiServerProvider provider;
_CollectionHarness._({required this.database, required MediaServerClient client}) {
manager = MultiServerManager()..debugRegisterClientForTesting(client);
provider = testMultiServerProvider(manager);
}
factory _CollectionHarness.plex() {
final database = AppDatabase.forTesting(NativeDatabase.memory());
PlexApiCache.initialize(database);
final client = testPlexClient(
config: PlexConfig(
baseUrl: 'https://plex.example.com',
token: 'token',
clientIdentifier: 'client-id',
product: 'Plezy',
version: 'test',
),
serverId: _serverId,
httpClient: MockClient((request) async {
if (request.url.path != '/library/sections/music/collections') {
return http.Response('not found', 404);
}
return http.Response(
jsonEncode({
'MediaContainer': {
'size': 1,
'totalSize': 1,
'Metadata': [
{'ratingKey': 'collection-1', 'type': 'collection', 'title': 'Music Collection', 'childCount': 4},
],
},
}),
200,
headers: {'content-type': 'application/json'},
);
}),
);
return _CollectionHarness._(database: database, client: client);
}
factory _CollectionHarness.jellyfin() {
final database = AppDatabase.forTesting(NativeDatabase.memory());
JellyfinApiCache.initialize(database);
final client = JellyfinClient.forTesting(
connection: testJellyfinConnection(machineId: _jellyfinServerId),
httpClient: MockClient((request) async {
if (request.url.path == '/Users/user-1/Views') {
return http.Response(
jsonEncode({
'Items': [
{'Id': 'boxsets-root', 'Name': 'Collections', 'CollectionType': 'boxsets'},
],
}),
200,
headers: {'content-type': 'application/json'},
);
}
if (request.url.path == '/Items') {
return http.Response(
jsonEncode({
'TotalRecordCount': 1,
'Items': [
{
'Id': 'video-collection-1',
'Name': 'Movie Collection',
'Type': 'BoxSet',
'MediaType': 'Video',
'ParentId': 'boxsets-root',
},
],
}),
200,
headers: {'content-type': 'application/json'},
);
}
return http.Response('not found', 404);
}),
);
return _CollectionHarness._(database: database, client: client);
}
Future<void> dispose() async {
provider.dispose();
manager.dispose();
await database.close();
}
}