Files
ReelShort/test/player/scrub_preview_card_test.dart
T

172 lines
4.9 KiB
Dart

// Author: fruit.com.cn
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/services/playback_thumbnail_service.dart';
import 'package:fruitshorts/core/data/services/sprite_thumbnail_axis.dart';
import 'package:fruitshorts/pages/player/widgets/scrub_preview_card.dart';
void main() {
testWidgets('should_render_sprite_crop_without_requesting_local_frame', (
tester,
) async {
final image = await _createImage(320, 180);
final cue = const SpriteThumbnailCue(
start: Duration.zero,
end: Duration(seconds: 2),
spriteFile: 'sprite_001.jpg',
sourceRect: Rect.fromLTWH(0, 0, 160, 90),
);
final axis = _FakeAxisSource(
axisReady: true,
frame: SpriteThumbnailFrame(
image: image,
sourceRect: const Rect.fromLTWH(0, 0, 160, 90),
spriteUrl: 'https://cdn.test/sprite_001.jpg',
cue: cue,
),
);
final loader = _CountingThumbnailLoader();
final localService = _localService(loader);
addTearDown(image.dispose);
addTearDown(axis.dispose);
addTearDown(localService.dispose);
await tester.pumpWidget(
_card(
localService: localService,
axis: axis,
thumbnailsUrl: 'https://cdn.test/thumbnails.vtt',
),
);
await tester.pump();
final preview = tester.widget<SpriteCropPreview>(
find.byKey(const Key('scrub-preview-sprite')),
);
expect(preview.sourceRect, const Rect.fromLTWH(0, 0, 160, 90));
expect(find.byKey(const Key('scrub-preview-frame')), findsNothing);
expect(loader.calls, 0);
});
testWidgets('should_fallback_to_local_frame_when_thumbnails_url_is_missing', (
tester,
) async {
final loader = _CountingThumbnailLoader();
final localService = _localService(loader);
final axis = _FakeAxisSource(axisReady: false);
addTearDown(axis.dispose);
addTearDown(localService.dispose);
await tester.pumpWidget(
_card(localService: localService, axis: axis, thumbnailsUrl: null),
);
await tester.pump();
expect(loader.calls, 1);
expect(find.byKey(const Key('scrub-preview-poster')), findsOneWidget);
expect(find.byKey(const Key('scrub-preview-sprite')), findsNothing);
});
testWidgets('should_keep_poster_when_axis_and_local_frame_both_fail', (
tester,
) async {
final loader = _CountingThumbnailLoader();
final localService = _localService(loader);
final axis = _FakeAxisSource(axisReady: true);
addTearDown(axis.dispose);
addTearDown(localService.dispose);
await tester.pumpWidget(
_card(
localService: localService,
axis: axis,
thumbnailsUrl: 'https://cdn.test/empty.vtt',
),
);
await tester.pump();
expect(loader.calls, 1);
expect(find.byKey(const Key('scrub-preview-poster')), findsOneWidget);
expect(find.byKey(const Key('scrub-preview-sprite')), findsNothing);
expect(find.byKey(const Key('scrub-preview-frame')), findsNothing);
});
}
Widget _card({
required PlaybackThumbnailService localService,
required SpriteThumbnailAxisSource axis,
required String? thumbnailsUrl,
}) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ScrubPreviewCard(
service: localService,
spriteAxisService: axis,
thumbnailsUrl: thumbnailsUrl,
episodeId: 'episode-1',
posterUrl: '',
position: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
),
),
),
);
}
PlaybackThumbnailService _localService(PlaybackThumbnailFrameLoader loader) {
return PlaybackThumbnailService(
frameLoader: loader,
sourceResolver: (_) =>
const PlaybackThumbnailSource(remoteUrl: 'https://example/video.mp4'),
prefetchDelay: const Duration(hours: 1),
);
}
class _FakeAxisSource extends ChangeNotifier
implements SpriteThumbnailAxisSource {
_FakeAxisSource({required this.axisReady, this.frame});
final bool axisReady;
final SpriteThumbnailFrame? frame;
@override
SpriteThumbnailFrame? cachedFrameAt(
String? thumbnailsUrl,
Duration position,
) => frame;
@override
bool hasAxis(String? thumbnailsUrl) => axisReady;
}
class _CountingThumbnailLoader implements PlaybackThumbnailFrameLoader {
int calls = 0;
@override
Future<ByteData?> load(PlaybackThumbnailFrameRequest request) async {
calls++;
return null;
}
@override
void dispose() {}
}
Future<ui.Image> _createImage(int width, int height) async {
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
canvas.drawRect(
Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()),
Paint()..color = Colors.orange,
);
final picture = recorder.endRecording();
final image = await picture.toImage(width, height);
picture.dispose();
return image;
}