245 lines
8.5 KiB
Dart
245 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:plezy/focus/focus_glow_overlay.dart';
|
|
import 'package:plezy/focus/focus_theme.dart';
|
|
import 'package:plezy/focus/input_mode_tracker.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/services/settings_service.dart';
|
|
import 'package:plezy/theme/mono_theme.dart';
|
|
import 'package:plezy/utils/layout_constants.dart';
|
|
import 'package:plezy/utils/platform_detector.dart';
|
|
import 'package:plezy/widgets/focusable_media_card.dart';
|
|
import 'package:plezy/widgets/media_card.dart';
|
|
import 'package:plezy/widgets/media_grid_delegate.dart';
|
|
|
|
import '../test_helpers/prefs.dart';
|
|
import '../test_helpers/media_items.dart';
|
|
|
|
void main() {
|
|
TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
setUp(() async {
|
|
resetSharedPreferencesForTest();
|
|
SettingsService.resetForTesting();
|
|
await SettingsService.getInstance();
|
|
});
|
|
|
|
tearDown(() {
|
|
TvDetectionService.debugSetAppleTVOverride(null);
|
|
});
|
|
|
|
test('full bleed grid delegates use image aspect ratios', () {
|
|
expect(MediaGridDelegate.aspectRatioFor(fullBleedImage: true), GridLayoutConstants.fullCardPosterAspectRatio);
|
|
expect(
|
|
MediaGridDelegate.aspectRatioFor(useWideAspectRatio: true, fullBleedImage: true),
|
|
GridLayoutConstants.episodeThumbnailAspectRatio,
|
|
);
|
|
expect(MediaGridDelegate.aspectRatioFor(useWideAspectRatio: true), GridLayoutConstants.episodeGridCellAspectRatio);
|
|
});
|
|
|
|
testWidgets('full bleed grid delegates use scaled gutters', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
tester.view.devicePixelRatio = 1.0;
|
|
tester.view.physicalSize = const Size(1280, 720);
|
|
addTearDown(() {
|
|
tester.view.resetDevicePixelRatio();
|
|
tester.view.resetPhysicalSize();
|
|
});
|
|
|
|
late SliverGridDelegateWithMaxCrossAxisExtent delegate;
|
|
await tester.pumpWidget(
|
|
_TestApp(
|
|
child: Builder(
|
|
builder: (context) {
|
|
delegate = MediaGridDelegate.createDelegate(
|
|
context: context,
|
|
density: LibraryDensity.defaultValue,
|
|
fullBleedImage: true,
|
|
);
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(delegate.crossAxisSpacing, greaterThan(0));
|
|
expect(delegate.mainAxisSpacing, delegate.crossAxisSpacing);
|
|
expect(delegate.crossAxisSpacing, GridLayoutConstants.fullCardGridSpacingForScale(0.85));
|
|
});
|
|
|
|
testWidgets('full bleed grid media cards hide text when constrained by a grid cell', (tester) async {
|
|
final item = testMediaItem(
|
|
id: 'movie_1',
|
|
backend: MediaBackend.plex,
|
|
kind: MediaKind.movie,
|
|
title: 'Hidden Movie',
|
|
year: 2024,
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_TestApp(
|
|
child: SizedBox(
|
|
width: 200,
|
|
height: 300,
|
|
child: MediaCard(item: item, forceGridMode: true, fullBleedImage: true, isOffline: true),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Hidden Movie'), findsNothing);
|
|
expect(find.text('2024'), findsNothing);
|
|
expect(tester.getSize(find.byType(InkWell)), const Size(200, 300));
|
|
});
|
|
|
|
testWidgets('standard grid media cards still show text', (tester) async {
|
|
final item = testMediaItem(
|
|
id: 'movie_1',
|
|
backend: MediaBackend.plex,
|
|
kind: MediaKind.movie,
|
|
title: 'Visible Movie',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
_TestApp(
|
|
child: SizedBox(width: 200, height: 330, child: MediaCard(item: item, forceGridMode: true, isOffline: true)),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Visible Movie'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('full bleed flag does not hide list media card text', (tester) async {
|
|
final item = testMediaItem(id: 'movie_1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'List Movie');
|
|
|
|
await tester.pumpWidget(
|
|
_TestApp(
|
|
child: SizedBox(
|
|
width: 420,
|
|
height: 160,
|
|
child: MediaCard(item: item, forceListMode: true, fullBleedImage: true, isOffline: true),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('List Movie'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('full bleed focusable media card lifts the glow into an overlay above siblings', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
final focusNode = FocusNode(debugLabel: 'full_bleed_card');
|
|
addTearDown(focusNode.dispose);
|
|
|
|
await tester.pumpWidget(_fullCardHarness(focusNode: focusNode, fullBleed: true));
|
|
|
|
// Unfocused: the overlay glow (and its leader) is not mounted.
|
|
expect(find.byType(CompositedTransformTarget), findsNothing);
|
|
expect(find.byType(CompositedTransformFollower), findsNothing);
|
|
|
|
focusNode.requestFocus();
|
|
await tester.pump(); // focus change mounts the overlay portal + leader
|
|
await tester.pump(); // leaderSize resolves, glow fades in
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
|
|
// The glow now follows the card from the overlay, so it paints above siblings.
|
|
expect(find.byType(FocusGlowOverlay), findsOneWidget);
|
|
expect(find.byType(CompositedTransformTarget), findsOneWidget);
|
|
expect(find.byType(CompositedTransformFollower), findsOneWidget);
|
|
expect(
|
|
find.descendant(of: find.byType(CompositedTransformFollower), matching: find.byType(CustomPaint)),
|
|
findsOneWidget,
|
|
);
|
|
|
|
// The crisp focus border stays in-card as a foreground decoration; the glow
|
|
// is no longer in the background decoration.
|
|
final borderContainer = tester.widget<AnimatedContainer>(
|
|
find.descendant(of: find.byType(FocusGlowOverlay), matching: find.byType(AnimatedContainer)).first,
|
|
);
|
|
expect(borderContainer.decoration, isNull);
|
|
final border = (borderContainer.foregroundDecoration as BoxDecoration).border as Border;
|
|
expect(border.top.strokeAlign, BorderSide.strokeAlignOutside);
|
|
|
|
// The glow itself is two shadows.
|
|
expect(FocusTheme.focusGlowShadows(const Color(0xFFFFFFFF)), hasLength(2));
|
|
});
|
|
|
|
testWidgets('non full bleed card does not use the overlay glow', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
await tester.pumpWidget(_fullCardHarness(focusNode: focusNode, fullBleed: false));
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(find.byType(FocusGlowOverlay), findsNothing);
|
|
expect(find.byType(CompositedTransformFollower), findsNothing);
|
|
});
|
|
|
|
testWidgets('overlay glow fades out and unmounts when focus is lost', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(true);
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
await tester.pumpWidget(_fullCardHarness(focusNode: focusNode, fullBleed: true));
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(find.byType(CompositedTransformFollower), findsOneWidget);
|
|
|
|
focusNode.unfocus();
|
|
await tester.pump(); // begin fade-out
|
|
await tester.pump(const Duration(milliseconds: 300)); // fade completes -> hide
|
|
await tester.pump(); // rebuild drops the gated-out leader/portal
|
|
expect(find.byType(CompositedTransformFollower), findsNothing);
|
|
});
|
|
|
|
testWidgets('touch mode shows no overlay glow on a full bleed card', (tester) async {
|
|
TvDetectionService.debugSetAppleTVOverride(false); // pointer mode
|
|
final focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
await tester.pumpWidget(_fullCardHarness(focusNode: focusNode, fullBleed: true));
|
|
focusNode.requestFocus();
|
|
await tester.pump();
|
|
await tester.pump();
|
|
|
|
expect(find.byType(CompositedTransformFollower), findsNothing);
|
|
});
|
|
}
|
|
|
|
Widget _fullCardHarness({required FocusNode focusNode, required bool fullBleed}) {
|
|
final item = testMediaItem(id: 'movie_1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Focused Movie');
|
|
return InputModeTracker(
|
|
child: _TestApp(
|
|
child: SizedBox(
|
|
width: 200,
|
|
height: 300,
|
|
child: FocusableMediaCard(
|
|
item: item,
|
|
forceGridMode: true,
|
|
fullBleedImage: fullBleed,
|
|
focusNode: focusNode,
|
|
isOffline: true,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
class _TestApp extends StatelessWidget {
|
|
final Widget child;
|
|
|
|
const _TestApp({required this.child});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
theme: monoTheme(dark: true),
|
|
home: Scaffold(body: Center(child: child)),
|
|
);
|
|
}
|
|
}
|