172 lines
4.9 KiB
Dart
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;
|
|
}
|