1072 lines
35 KiB
Dart
1072 lines
35 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
import 'dart:async';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:shared_preferences_platform_interface/in_memory_shared_preferences_async.dart';
|
|
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
|
|
|
|
import 'package:fruitshorts/app.dart';
|
|
import 'package:fruitshorts/core/data/models/content_category.dart';
|
|
import 'package:fruitshorts/core/data/models/drama.dart';
|
|
import 'package:fruitshorts/core/data/models/home_feed.dart';
|
|
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
|
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
|
import 'package:fruitshorts/core/data/repositories/watch_history_repository.dart';
|
|
import 'package:fruitshorts/core/data/services/playback_thumbnail_service.dart';
|
|
import 'package:fruitshorts/core/theme/app_colors.dart';
|
|
import 'package:fruitshorts/core/theme/app_dimens.dart';
|
|
import 'package:fruitshorts/core/theme/app_theme.dart';
|
|
import 'package:fruitshorts/l10n/app_localizations.dart';
|
|
import 'package:fruitshorts/pages/home/home_feed_channel.dart';
|
|
import 'package:fruitshorts/pages/player/widgets/home_feed_header.dart';
|
|
import 'package:fruitshorts/pages/player/widgets/home_feed_overlay.dart';
|
|
import 'package:fruitshorts/pages/player/player_view.dart';
|
|
import 'package:video_player/video_player.dart';
|
|
|
|
import '../support/fake_catalog.dart';
|
|
import '../support/fake_category.dart';
|
|
import '../support/fake_haptics.dart';
|
|
import '../support/fake_interaction_repositories.dart';
|
|
import '../support/fake_notice_repository.dart';
|
|
import '../support/widget_player.dart';
|
|
|
|
void main() {
|
|
testWidgets('should_render_three_column_explorer_grid_on_home', (
|
|
tester,
|
|
) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
|
|
final catalog = FakeCatalogRepository(
|
|
banners: const [
|
|
FeedBanner(
|
|
id: 'banner-1',
|
|
imageUrl: '/files/banner.jpg',
|
|
linkType: 'DRAMA',
|
|
linkValue: 'the-signal-demo',
|
|
),
|
|
],
|
|
);
|
|
await tester.pumpWidget(
|
|
LumaDramaApp(
|
|
playerFactory: createWidgetPlayer,
|
|
catalogRepository: catalog,
|
|
categoryRepository: FakeCategoryRepository(
|
|
catalog: catalog,
|
|
categories: const [
|
|
ContentCategory(id: 'costume', name: '古装'),
|
|
ContentCategory(id: 'community', name: '社群'),
|
|
],
|
|
),
|
|
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
|
favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas),
|
|
playbackRepository: FakePlaybackRepository(),
|
|
commentRepository: FakeCommentRepository(),
|
|
interactionRepository: FakeInteractionRepository(),
|
|
noticeRepository: FakeNoticeRepository(),
|
|
haptics: FakeHaptics(),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('home-grid')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-grid-cards')), findsOneWidget);
|
|
final grid = tester.widget<SliverGrid>(
|
|
find.byKey(const Key('home-grid-cards')),
|
|
);
|
|
final delegate =
|
|
grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount;
|
|
expect(delegate.crossAxisCount, 3);
|
|
expect(delegate.crossAxisSpacing, AppDimens.homeGridGap);
|
|
expect(delegate.mainAxisSpacing, AppDimens.homeGridRowGap);
|
|
expect(find.byKey(const Key('home-section-featured')), findsNothing);
|
|
expect(find.byKey(const Key('home-section-category-古装')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-section-category-社群')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-secondary-tags')), findsNothing);
|
|
expect(find.byKey(const Key('home-grid-filter')), findsNothing);
|
|
final searchBox = find.descendant(
|
|
of: find.byKey(const Key('home-search-entry')),
|
|
matching: find.byType(Container),
|
|
);
|
|
expect(tester.getSize(searchBox).height, AppDimens.searchHeight);
|
|
expect(
|
|
(tester.widget<Container>(searchBox).decoration! as BoxDecoration)
|
|
.borderRadius,
|
|
BorderRadius.circular(AppDimens.searchRadius),
|
|
);
|
|
final placeholder = tester.widget<Text>(
|
|
find.text('Search dramas, actors, genres...'),
|
|
);
|
|
expect(placeholder.style?.fontSize, AppDimens.searchPlaceholderFont);
|
|
expect(placeholder.style?.color, AppColors.textSecondary);
|
|
final selectedPrimary = tester.widget<AnimatedDefaultTextStyle>(
|
|
find.byKey(const Key('home-primary-label-costume')),
|
|
);
|
|
final unselectedPrimary = tester.widget<AnimatedDefaultTextStyle>(
|
|
find.byKey(const Key('home-primary-label-community')),
|
|
);
|
|
expect(
|
|
selectedPrimary.style.fontSize,
|
|
AppDimens.homePrimaryTabSelectedFont,
|
|
);
|
|
expect(selectedPrimary.style.fontWeight, FontWeight.w700);
|
|
expect(selectedPrimary.style.color, AppColors.navSelected);
|
|
expect(selectedPrimary.duration, AppDimens.homePrimaryTabAnimationDuration);
|
|
expect(unselectedPrimary.style.fontSize, AppDimens.homePrimaryTabFont);
|
|
expect(unselectedPrimary.style.fontWeight, FontWeight.w400);
|
|
expect(unselectedPrimary.style.color, AppColors.textTabInactive);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('home-section-category-古装')),
|
|
matching: find.byType(Container),
|
|
),
|
|
findsNothing,
|
|
);
|
|
final primaryStart = tester.getTopLeft(find.text('古装'));
|
|
final pageStart = tester.getTopLeft(searchBox);
|
|
expect(primaryStart.dx, pageStart.dx);
|
|
final primaryGap =
|
|
tester.getTopLeft(find.text('社群')).dx -
|
|
tester.getBottomRight(find.text('古装')).dx;
|
|
expect(primaryGap, closeTo(AppDimens.homePrimaryCategoryGap, 0.1));
|
|
expect(find.byKey(const Key('home-primary-page-view')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('should_show_immersive_player_and_open_playlist_in_recommend', (
|
|
tester,
|
|
) async {
|
|
SharedPreferencesAsyncPlatform.instance =
|
|
InMemorySharedPreferencesAsync.empty();
|
|
tester.view.physicalSize = const Size(390, 844);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final catalog = FakeCatalogRepository();
|
|
await tester.pumpWidget(
|
|
LumaDramaApp(
|
|
playerFactory: createWidgetPlayer,
|
|
catalogRepository: catalog,
|
|
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
|
watchHistoryRepository: InMemoryWatchHistoryRepository(),
|
|
favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas),
|
|
playbackRepository: FakePlaybackRepository(),
|
|
commentRepository: FakeCommentRepository(),
|
|
interactionRepository: FakeInteractionRepository(),
|
|
noticeRepository: FakeNoticeRepository(),
|
|
haptics: FakeHaptics(),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('tab-recommend')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('home-grid')), findsNothing);
|
|
expect(find.byKey(const Key('recommend-page')), findsOneWidget);
|
|
expect(find.byKey(const Key('recommend-feed-player')), findsOneWidget);
|
|
expect(find.byType(PlayerPage), findsOneWidget);
|
|
expect(tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode, isTrue);
|
|
expect(find.byKey(const Key('recommend-section-hits')), findsNothing);
|
|
|
|
await tester.tap(find.byKey(const Key('recommend-tab-playlist')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
|
|
|
|
await tester.tap(
|
|
find.byKey(const Key('recommend-card-hits-the-signal-demo')),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
tester.widget<PlayerPage>(find.byType(PlayerPage).last).homeMode,
|
|
isFalse,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_render_new_feed_structure_with_formatted_counts', (
|
|
tester,
|
|
) async {
|
|
const drama = Drama(
|
|
id: 'feed-drama',
|
|
title: 'Feed Drama',
|
|
synopsis:
|
|
'A long synopsis follows the heroine through a dangerous city, '
|
|
'a hidden identity, and a final choice that changes everyone around '
|
|
'her forever.',
|
|
posterUrl: '',
|
|
genre: 'Romance',
|
|
category: 'Urban',
|
|
totalEpisodes: 8,
|
|
freeEpisodes: 8,
|
|
viewCount: 419000,
|
|
likeCount: 1006,
|
|
favoriteCount: 32000,
|
|
commentCount: 12,
|
|
);
|
|
var episodesOpened = 0;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: drama,
|
|
episode: const Episode(
|
|
id: 'feed-episode',
|
|
number: 2,
|
|
title: 'Episode 2',
|
|
isFree: true,
|
|
),
|
|
controller: null,
|
|
locked: false,
|
|
liked: true,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
likeCount: 1006,
|
|
favoriteCount: 32000,
|
|
commentCount: 12,
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onEpisodes: () => episodesOpened++,
|
|
onMore: () {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final seriesTitle = tester.widget<Text>(
|
|
find.byKey(const Key('home-feed-series-title')),
|
|
);
|
|
expect(seriesTitle.data, 'Feed Drama');
|
|
expect(find.text('Episode 2'), findsNothing);
|
|
expect(seriesTitle.style?.fontSize, 19);
|
|
expect(seriesTitle.style?.fontWeight, FontWeight.w600);
|
|
expect(seriesTitle.style?.color, AppColors.textPrimary);
|
|
final seriesChevron = tester.widget<Icon>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('home-feed-series-name')),
|
|
matching: find.byIcon(Icons.chevron_right_rounded),
|
|
),
|
|
);
|
|
expect(seriesChevron.size, 20);
|
|
expect(seriesChevron.color, AppColors.textPrimary);
|
|
expect(
|
|
tester.getTopLeft(find.byIcon(Icons.chevron_right_rounded)).dx -
|
|
tester
|
|
.getTopRight(find.byKey(const Key('home-feed-series-title')))
|
|
.dx,
|
|
4,
|
|
);
|
|
expect(find.byKey(const Key('home-feed-info-card')), findsNothing);
|
|
expect(find.byKey(const Key('home-feed-full-series')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-feed-tags')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-feed-synopsis-more')), findsOneWidget);
|
|
await tester.tap(find.byKey(const Key('home-feed-synopsis-more')));
|
|
await tester.pump();
|
|
expect(
|
|
tester.widget<Text>(find.byKey(const Key('home-feed-synopsis'))).maxLines,
|
|
isNull,
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<Text>(find.byKey(const Key('home-feed-episode-progress')))
|
|
.data,
|
|
'Episode 2 / 8 total',
|
|
);
|
|
expect(find.byKey(const Key('home-feed-meta-pills')), findsNothing);
|
|
expect(find.byKey(const Key('home-feed-ranking-pill')), findsNothing);
|
|
expect(find.byKey(const Key('home-feed-view-pill')), findsNothing);
|
|
expect(find.text('Romance Urban'), findsOneWidget);
|
|
final genreText = tester.widget<Text>(find.text('Romance Urban'));
|
|
expect(genreText.style?.fontSize, 13);
|
|
expect(genreText.style?.color, AppColors.overlayLabel);
|
|
final episodeProgress = tester.widget<Text>(
|
|
find.byKey(const Key('home-feed-episode-progress')),
|
|
);
|
|
expect(episodeProgress.maxLines, 1);
|
|
expect(episodeProgress.softWrap, isFalse);
|
|
expect(episodeProgress.style?.fontSize, 13);
|
|
expect(episodeProgress.style?.color, AppColors.overlayLabel);
|
|
expect(
|
|
tester
|
|
.getTopLeft(find.byKey(const Key('home-feed-episode-progress')))
|
|
.dx -
|
|
tester.getTopRight(find.text('Romance Urban')).dx,
|
|
8,
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.byKey(const Key('rail-like-count'))).data,
|
|
'1006',
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
|
|
'3.2万',
|
|
);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('rail-like')),
|
|
matching: find.byIcon(Icons.favorite_rounded),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('rail-save')),
|
|
matching: find.byIcon(Icons.bookmark_border_rounded),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byKey(const Key('rail-episodes')), findsOneWidget);
|
|
expect(
|
|
find.descendant(
|
|
of: find.byKey(const Key('rail-episodes')),
|
|
matching: find.byIcon(Icons.layers_rounded),
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byKey(const Key('rail-comments')), findsNothing);
|
|
expect(find.byKey(const Key('rail-share')), findsNothing);
|
|
expect(find.text('Comments'), findsNothing);
|
|
expect(
|
|
tester.getRect(find.byKey(const Key('home-feed-action-rail'))).right,
|
|
closeTo(
|
|
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 8,
|
|
0.1,
|
|
),
|
|
);
|
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
|
expect(
|
|
find.byKey(const Key('home-feed-progress-inactive')),
|
|
findsOneWidget,
|
|
);
|
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsNothing);
|
|
await tester.tap(find.byKey(const Key('rail-episodes')));
|
|
expect(episodesOpened, 1);
|
|
});
|
|
|
|
testWidgets('should_show_static_track_without_seek_when_feed_is_locked', (
|
|
tester,
|
|
) async {
|
|
var scrubStarts = 0;
|
|
var seeks = 0;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'locked-feed',
|
|
title: 'Locked Feed',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
),
|
|
episode: const Episode(
|
|
id: 'locked-feed-1',
|
|
number: 1,
|
|
title: 'Locked',
|
|
isFree: false,
|
|
locked: true,
|
|
),
|
|
controller: null,
|
|
locked: true,
|
|
liked: false,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onMore: () {},
|
|
onScrubStart: () => scrubStarts++,
|
|
onSeek: (_) => seeks++,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
|
expect(find.byKey(const Key('progress-loading-highlight')), findsNothing);
|
|
expect(
|
|
find.byKey(const Key('home-feed-progress-inactive')),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
tester
|
|
.getSize(find.byKey(const Key('home-feed-progress-inactive')))
|
|
.height,
|
|
2,
|
|
);
|
|
|
|
await tester.drag(
|
|
find.byKey(const Key('home-feed-progress')),
|
|
const Offset(120, 0),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(scrubStarts, 0);
|
|
expect(seeks, 0);
|
|
});
|
|
|
|
testWidgets('should_show_static_track_when_feed_video_is_uninitialized', (
|
|
tester,
|
|
) async {
|
|
final controller = ValueNotifier<VideoPlayerValue>(
|
|
const VideoPlayerValue.uninitialized(),
|
|
);
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'loading-feed',
|
|
title: 'Loading Feed',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
),
|
|
episode: const Episode(
|
|
id: 'loading-feed-1',
|
|
number: 1,
|
|
title: 'Loading',
|
|
isFree: true,
|
|
),
|
|
controller: controller,
|
|
locked: false,
|
|
liked: false,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onMore: () {},
|
|
onSeek: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
|
expect(
|
|
find.byKey(const Key('home-feed-progress-inactive')),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_seek_and_open_player_from_feed_actions', (tester) async {
|
|
var opened = 0;
|
|
Duration? seeked;
|
|
final scrubVisibility = <bool>[];
|
|
final thumbnailService = _nullThumbnailService();
|
|
addTearDown(thumbnailService.dispose);
|
|
final controller = ValueNotifier<VideoPlayerValue>(
|
|
VideoPlayerValue(
|
|
duration: const Duration(seconds: 100),
|
|
position: const Duration(seconds: 10),
|
|
isInitialized: true,
|
|
),
|
|
);
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'action-drama',
|
|
title: 'Action Drama',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
totalEpisodes: 8,
|
|
viewCount: 1200,
|
|
),
|
|
episode: const Episode(
|
|
id: 'action-episode',
|
|
number: 1,
|
|
title: 'Episode 1',
|
|
isFree: true,
|
|
),
|
|
controller: controller,
|
|
locked: false,
|
|
liked: false,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () => opened++,
|
|
onMore: () {},
|
|
onScrubStart: () {},
|
|
onScrubVisibilityChanged: scrubVisibility.add,
|
|
onSeek: (value) => seeked = value,
|
|
thumbnailService: thumbnailService,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final bar = tester.getRect(find.byKey(const Key('home-feed-full-series')));
|
|
expect(bar.height, 44);
|
|
final overlayRect = tester.getRect(
|
|
find.byKey(const Key('home-feed-gesture')),
|
|
);
|
|
expect(bar.left, closeTo(overlayRect.left + 14, 0.1));
|
|
expect(bar.width, closeTo(overlayRect.width * 0.7, 1));
|
|
expect(
|
|
tester.getCenter(find.byKey(const Key('home-feed-progress-track'))).dy -
|
|
bar.bottom,
|
|
greaterThanOrEqualTo(20),
|
|
);
|
|
final buttonBackground =
|
|
tester
|
|
.widget<DecoratedBox>(
|
|
find.byKey(const Key('home-feed-full-series-bg')),
|
|
)
|
|
.decoration
|
|
as BoxDecoration;
|
|
expect((buttonBackground.gradient! as LinearGradient).colors, [
|
|
AppColors.actionRed,
|
|
AppColors.blue,
|
|
]);
|
|
expect(
|
|
tester.getRect(find.text('Watch all')).center.dy,
|
|
closeTo(bar.center.dy, 2),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress-thumb'))),
|
|
const Size(6, 6),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress'))).height,
|
|
greaterThanOrEqualTo(32),
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<GestureDetector>(find.byKey(const Key('home-feed-progress')))
|
|
.dragStartBehavior,
|
|
DragStartBehavior.down,
|
|
);
|
|
final idleTrackCenter = tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-track')))
|
|
.center
|
|
.dy;
|
|
expect(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-thumb')))
|
|
.center
|
|
.dy,
|
|
closeTo(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-track')))
|
|
.center
|
|
.dy,
|
|
0.5,
|
|
),
|
|
);
|
|
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.byKey(const Key('home-feed-progress'))),
|
|
);
|
|
await gesture.moveBy(const Offset(120, 0));
|
|
await tester.pump();
|
|
expect(scrubVisibility.last, isTrue);
|
|
expect(find.byKey(const Key('home-feed-series-title')), findsOneWidget);
|
|
expect(find.byKey(const Key('rail-save')), findsOneWidget);
|
|
expect(
|
|
tester
|
|
.widget<AnimatedOpacity>(
|
|
find.byKey(const Key('home-feed-content-opacity')),
|
|
)
|
|
.opacity,
|
|
0,
|
|
);
|
|
expect(
|
|
tester
|
|
.widget<IgnorePointer>(
|
|
find.byKey(const Key('home-feed-content-ignore')),
|
|
)
|
|
.ignoring,
|
|
isTrue,
|
|
);
|
|
expect(find.byKey(const Key('scrub-preview-time-bubble')), findsOneWidget);
|
|
final bubbleRect = tester.getRect(
|
|
find.byKey(const Key('scrub-preview-time-bubble')),
|
|
);
|
|
expect(bubbleRect.left, greaterThanOrEqualTo(12));
|
|
expect(
|
|
bubbleRect.right,
|
|
lessThanOrEqualTo(
|
|
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 12,
|
|
),
|
|
);
|
|
expect(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-track')))
|
|
.center
|
|
.dy -
|
|
bubbleRect.bottom,
|
|
closeTo(16, 0.1),
|
|
);
|
|
final bubbleContainer = tester.widget<Container>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('scrub-preview-time-bubble')),
|
|
matching: find.byType(Container),
|
|
),
|
|
);
|
|
expect(
|
|
(bubbleContainer.decoration! as BoxDecoration).color,
|
|
AppColors.timeBubbleScrim,
|
|
);
|
|
expect(
|
|
bubbleContainer.padding,
|
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
|
|
);
|
|
await gesture.moveBy(const Offset(-600, 0));
|
|
await tester.pump();
|
|
expect(
|
|
tester.getRect(find.byKey(const Key('scrub-preview-time-bubble'))).left,
|
|
closeTo(12, 0.1),
|
|
);
|
|
await gesture.moveBy(const Offset(1200, 0));
|
|
await tester.pump();
|
|
expect(
|
|
tester.getRect(find.byKey(const Key('scrub-preview-time-bubble'))).right,
|
|
closeTo(
|
|
tester.getRect(find.byKey(const Key('home-feed-gesture'))).right - 12,
|
|
0.1,
|
|
),
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 130));
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress-thumb'))),
|
|
const Size(14, 14),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress-track'))).height,
|
|
6,
|
|
);
|
|
expect(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-track')))
|
|
.center
|
|
.dy,
|
|
closeTo(idleTrackCenter, 0.1),
|
|
);
|
|
expect(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-thumb')))
|
|
.center
|
|
.dy,
|
|
closeTo(
|
|
tester
|
|
.getRect(find.byKey(const Key('home-feed-progress-track')))
|
|
.center
|
|
.dy,
|
|
0.5,
|
|
),
|
|
);
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
expect(scrubVisibility.last, isFalse);
|
|
expect(find.byKey(const Key('home-feed-series-title')), findsOneWidget);
|
|
expect(
|
|
tester
|
|
.widget<AnimatedOpacity>(
|
|
find.byKey(const Key('home-feed-content-opacity')),
|
|
)
|
|
.opacity,
|
|
1,
|
|
);
|
|
expect(seeked, isNotNull);
|
|
expect(seeked!.inSeconds, greaterThan(0));
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress-thumb'))),
|
|
const Size(6, 6),
|
|
);
|
|
expect(
|
|
tester.getSize(find.byKey(const Key('home-feed-progress-track'))).height,
|
|
2,
|
|
);
|
|
|
|
await tester.tap(find.byKey(const Key('home-feed-full-series')));
|
|
await tester.pump();
|
|
expect(opened, 1);
|
|
});
|
|
|
|
testWidgets('should_debounce_feed_loading_and_keep_track_while_scrubbing', (
|
|
tester,
|
|
) async {
|
|
final controller = ValueNotifier<VideoPlayerValue>(
|
|
const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
),
|
|
);
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'debounce-drama',
|
|
title: 'Debounce Drama',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
totalEpisodes: 8,
|
|
),
|
|
episode: const Episode(
|
|
id: 'debounce-episode',
|
|
number: 1,
|
|
title: 'Episode 1',
|
|
isFree: true,
|
|
),
|
|
controller: controller,
|
|
locked: false,
|
|
liked: false,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onMore: () {},
|
|
onSeek: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
controller.value = const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
isBuffering: true,
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsOneWidget);
|
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
|
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsNothing);
|
|
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
|
|
|
controller.value = const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
);
|
|
await tester.pump();
|
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsOneWidget);
|
|
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.byKey(const Key('home-feed-progress'))),
|
|
);
|
|
await gesture.moveBy(const Offset(60, 0));
|
|
await tester.pump();
|
|
controller.value = const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
isBuffering: true,
|
|
);
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
|
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsOneWidget);
|
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
|
|
|
controller.value = const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('should_show_refresh_spinner_until_refresh_finishes', (
|
|
tester,
|
|
) async {
|
|
final completer = Completer<bool>();
|
|
var calls = 0;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedHeader(
|
|
l10n: AppLocalizations.of(context)!,
|
|
channel: HomeFeedChannel.recommend,
|
|
onChannelSelected: (_) {},
|
|
onRefresh: () {
|
|
calls++;
|
|
return completer.future;
|
|
},
|
|
onSearch: () {},
|
|
onMore: () {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(const Key('home-feed-refresh')));
|
|
await tester.pump();
|
|
expect(find.byKey(const Key('home-feed-refreshing')), findsOneWidget);
|
|
|
|
await tester.tap(find.byKey(const Key('home-feed-refresh')));
|
|
await tester.pump();
|
|
expect(calls, 1);
|
|
|
|
completer.complete(true);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('home-feed-refreshing')), findsNothing);
|
|
expect(find.byIcon(Icons.refresh_rounded), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_show_scrub_preview_card_from_home_progress', (
|
|
tester,
|
|
) async {
|
|
final controller = ValueNotifier<VideoPlayerValue>(
|
|
const VideoPlayerValue(
|
|
duration: Duration(seconds: 100),
|
|
position: Duration(seconds: 10),
|
|
isInitialized: true,
|
|
),
|
|
);
|
|
final service = _nullThumbnailService();
|
|
addTearDown(controller.dispose);
|
|
addTearDown(service.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'preview-drama',
|
|
title: 'Preview Drama',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
totalEpisodes: 8,
|
|
),
|
|
episode: const Episode(
|
|
id: 'preview-episode',
|
|
number: 1,
|
|
title: 'Episode 1',
|
|
isFree: true,
|
|
),
|
|
controller: controller,
|
|
locked: false,
|
|
liked: false,
|
|
saved: false,
|
|
visible: true,
|
|
boosting: false,
|
|
thumbnailService: service,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onMore: () {},
|
|
onScrubStart: () {},
|
|
onSeek: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.byKey(const Key('home-feed-progress'))),
|
|
);
|
|
await gesture.moveBy(const Offset(60, 0));
|
|
await tester.pump();
|
|
|
|
expect(find.byKey(const Key('scrub-preview-card')), findsOneWidget);
|
|
expect(find.byKey(const Key('scrub-preview-poster')), findsOneWidget);
|
|
expect(find.textContaining('/ 01:40'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(find.byKey(const Key('scrub-preview-card')), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_seek_from_series_bar_when_overlay_is_cleared', (
|
|
tester,
|
|
) async {
|
|
Duration? seeked;
|
|
final controller = ValueNotifier<VideoPlayerValue>(
|
|
VideoPlayerValue(
|
|
duration: const Duration(seconds: 100),
|
|
position: const Duration(seconds: 10),
|
|
isInitialized: true,
|
|
),
|
|
);
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.light(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Builder(
|
|
builder: (context) => Scaffold(
|
|
body: HomeFeedOverlay(
|
|
l10n: AppLocalizations.of(context)!,
|
|
drama: const Drama(
|
|
id: 'clear-drama',
|
|
title: 'Clear Drama',
|
|
synopsis: 'synopsis',
|
|
posterUrl: '',
|
|
totalEpisodes: 8,
|
|
),
|
|
episode: const Episode(
|
|
id: 'clear-episode',
|
|
number: 1,
|
|
title: 'Episode 1',
|
|
isFree: true,
|
|
),
|
|
controller: controller,
|
|
locked: false,
|
|
liked: false,
|
|
saved: false,
|
|
visible: false,
|
|
boosting: false,
|
|
onTogglePlay: () {},
|
|
onBoostStart: () {},
|
|
onBoostEnd: () {},
|
|
onLike: () {},
|
|
onSave: () {},
|
|
onComments: () {},
|
|
onShare: () {},
|
|
onDetail: () {},
|
|
onOpenFullPlayer: () {},
|
|
onMore: () {},
|
|
onScrubStart: () {},
|
|
onSeek: (value) => seeked = value,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('home-feed-full-series')), findsOneWidget);
|
|
expect(find.byKey(const Key('home-feed-progress')), findsOneWidget);
|
|
|
|
final gesture = await tester.startGesture(
|
|
tester.getCenter(find.byKey(const Key('home-feed-progress'))),
|
|
);
|
|
await gesture.moveBy(const Offset(80, 0));
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(seeked, isNotNull);
|
|
expect(seeked!.inSeconds, greaterThan(0));
|
|
});
|
|
}
|
|
|
|
PlaybackThumbnailService _nullThumbnailService() {
|
|
return PlaybackThumbnailService(
|
|
frameLoader: _NullThumbnailLoader(),
|
|
sourceResolver: (_) =>
|
|
const PlaybackThumbnailSource(remoteUrl: 'https://example/video.mp4'),
|
|
prefetchDelay: const Duration(hours: 1),
|
|
);
|
|
}
|
|
|
|
class _NullThumbnailLoader implements PlaybackThumbnailFrameLoader {
|
|
@override
|
|
Future<ByteData?> load(PlaybackThumbnailFrameRequest request) async => null;
|
|
|
|
@override
|
|
void dispose() {}
|
|
}
|