// Author: fruit.com.cn import 'dart:ui' as ui; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/services/sprite_thumbnail_asset_loader.dart'; import 'package:fruitshorts/core/data/services/sprite_thumbnail_axis.dart'; import 'package:fruitshorts/pages/player/widgets/player_opening_frame.dart'; import 'package:fruitshorts/pages/player/widgets/scrub_preview_card.dart'; const String _spriteVtt = ''' WEBVTT 00:00:00.000 --> 00:00:01.000 sprite_001.jpg#xywh=0,0,160,90 '''; void main() { test('should_choose_poster_before_thumbnails_when_both_are_available', () { final service = SpriteThumbnailAxisService(loader: _FakeSpriteLoader()); addTearDown(service.dispose); final frame = playerOpeningFrame( 'https://cdn.test/thumbnails.vtt', posterUrl: 'https://cdn.test/poster.jpg', spriteSource: service, fit: BoxFit.cover, ) as PlayerOpeningFrame; expect(frame.posterUrl, 'https://cdn.test/poster.jpg'); expect(frame.thumbnailsUrl, 'https://cdn.test/thumbnails.vtt'); expect(frame.fit, BoxFit.cover); expect( playerOpeningFallback( posterUrl: frame.posterUrl, thumbnailsUrl: frame.thumbnailsUrl, ), PlayerOpeningFallback.poster, ); }); testWidgets('should_use_poster_without_decoding_sprite_when_poster_exists', ( tester, ) async { final loader = _FakeSpriteLoader(); final service = SpriteThumbnailAxisService(loader: loader); addTearDown(service.dispose); await tester.pumpWidget( MaterialApp( home: PlayerOpeningFrame( posterUrl: 'https://cdn.test/poster.jpg', thumbnailsUrl: 'https://cdn.test/thumbnails.vtt', spriteSource: service, ), ), ); expect(find.byKey(const Key('player-opening-poster')), findsOneWidget); expect(find.byKey(const Key('player-opening-sprite')), findsNothing); expect(loader.vttCalls, 0); }); testWidgets('should_decode_fullscreen_poster_at_screen_width', ( tester, ) async { final service = SpriteThumbnailAxisService(loader: _FakeSpriteLoader()); addTearDown(service.dispose); await tester.pumpWidget( MaterialApp( home: MediaQuery( data: const MediaQueryData(devicePixelRatio: 3), child: Center( child: SizedBox( width: 390, height: 844, child: PlayerOpeningFrame( posterUrl: 'https://cdn.test/poster.jpg', thumbnailsUrl: '', spriteSource: service, ), ), ), ), ), ); final image = tester.widget( find.byKey(const Key('player-opening-poster')), ); final provider = image.image as ResizeImage; expect(provider.width, 1170); }); testWidgets('should_return_no_opening_frame_when_thumbnails_is_empty', ( tester, ) async { final service = SpriteThumbnailAxisService(loader: _FakeSpriteLoader()); addTearDown(service.dispose); expect(playerOpeningFrame(null, spriteSource: service), isNull); expect(playerOpeningFrame(' ', spriteSource: service), isNull); }); testWidgets('should_render_prewarmed_zero_frame_from_shared_cache', ( tester, ) async { final loader = _FakeSpriteLoader(); final service = SpriteThumbnailAxisService(loader: loader); addTearDown(service.dispose); const url = 'https://cdn.test/thumbnails.vtt'; await service.prepareAt(url, Duration.zero); await tester.pumpWidget( MaterialApp( home: PlayerOpeningFrame(thumbnailsUrl: url, spriteSource: service), ), ); await tester.pump(); expect(find.byKey(const Key('player-opening-sprite')), findsOneWidget); expect(loader.vttCalls, 1); expect(loader.spriteCalls, ['sprite_001.jpg']); }); testWidgets('should_load_sprite_when_not_prewarmed', (tester) async { final service = SpriteThumbnailAxisService(loader: _FakeSpriteLoader()); addTearDown(service.dispose); await tester.pumpWidget( MaterialApp( home: PlayerOpeningFrame( thumbnailsUrl: 'https://cdn.test/thumbnails.vtt', spriteSource: service, ), ), ); await tester.pumpAndSettle(); expect(find.byKey(const Key('player-opening-sprite')), findsOneWidget); }); testWidgets('should_use_same_fit_for_sprite_and_video', (tester) async { final service = SpriteThumbnailAxisService(loader: _FakeSpriteLoader()); addTearDown(service.dispose); const url = 'https://cdn.test/thumbnails.vtt'; await service.prepareAt(url, Duration.zero); await tester.pumpWidget( MaterialApp( home: PlayerOpeningFrame( thumbnailsUrl: url, spriteSource: service, fit: BoxFit.contain, ), ), ); final preview = tester.widget( find.byKey(const Key('player-opening-sprite')), ); expect(preview.fit, BoxFit.contain); }); } class _FakeSpriteLoader implements SpriteThumbnailAssetLoader { int vttCalls = 0; final List spriteCalls = []; @override Future loadVtt(Uri uri) async { vttCalls++; return _spriteVtt; } @override Future loadSprite(Uri uri) async { spriteCalls.add(uri.pathSegments.last); return SpriteThumbnailDecodedImage( image: await _createImage(320, 180), originalWidth: 320, originalHeight: 180, ); } } Future _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.blue, ); final picture = recorder.endRecording(); final image = await picture.toImage(width, height); picture.dispose(); return image; }