feat(home): 首页改为三列探索网格

This commit is contained in:
Zhou Weidong committed 2026-10-22 17:03:45 +08:00
1 parent 6dea9d479e
commit dd96792e33
29 files changed
+1912 -256

No files matched your search

+1 -1
View File
@@ -151,7 +151,7 @@ Future<void> _pumpFilterPage(WidgetTester tester) async {
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.tap(find.byKey(const Key('home-search-entry')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('theater-toolbar-filter')));
await tester.pumpAndSettle();
+3 -1
View File
@@ -455,7 +455,9 @@ Future<void> _pumpTheater(
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.tap(find.byKey(const Key('home-search-entry')));
await tester.pumpAndSettle();
FocusManager.instance.primaryFocus?.unfocus();
await tester.pumpAndSettle();
}
+1 -1
View File
@@ -303,7 +303,7 @@ Future<void> _pumpTheater(
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.tap(find.byKey(const Key('home-search-entry')));
await tester.pumpAndSettle();
}
+1 -1
View File
@@ -10,7 +10,7 @@ import 'package:fruitshorts/core/data/services/catalog_retry.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/widgets/catalog_endpoint_editor.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/home/home_view.dart';
import 'package:fruitshorts/pages/home/widgets/home_failure_view.dart';
void main() {
tearDown(AppConfig.clearRuntimeBaseUrl);
+128 -62
View File
@@ -7,91 +7,157 @@ 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/widgets/home_drama_grid.dart';
import 'package:fruitshorts/pages/home/widgets/home_feed_card.dart';
void main() {
testWidgets('should_render_compact_cover_badges_without_plus_action', (
testWidgets('should_render_grid_card_cover_badge_views_title_and_tags', (
tester,
) async {
var taps = 0;
await _pumpCard(
tester,
drama: _drama(
id: 'anime-card',
title: 'A very long home grid drama title that must wrap',
dramaType: 'ANIME',
viewCount: 444500,
),
onTap: () => taps++,
);
final card = find.byKey(const Key('home-feed-card-anime-card'));
final container = tester.widget<Container>(card);
final decoration = container.decoration! as BoxDecoration;
expect(decoration.color, AppColors.contentCard);
expect(decoration.boxShadow, isNull);
expect(
decoration.borderRadius,
BorderRadius.circular(AppDimens.homeCardRadius),
);
expect((decoration.border! as Border).top.width, 1);
expect((decoration.border! as Border).top.color, AppColors.contentBorder);
final cover = find.byKey(const Key('home-feed-cover-anime-card'));
final coverSize = tester.getSize(cover);
expect(coverSize.height / coverSize.width, closeTo(6 / 5, 0.02));
expect(
find.byKey(const Key('home-feed-badge-anime-anime-card')),
findsOneWidget,
);
expect(find.text('Anime'), findsOneWidget);
expect(find.byKey(const Key('home-feed-views-anime-card')), findsOneWidget);
final views = find.byKey(const Key('home-feed-views-anime-card'));
final viewsText = tester.widget<Text>(
find.descendant(of: views, matching: find.byType(Text)),
);
expect(viewsText.data, startsWith('44.'));
expect(
tester
.widget<Text>(find.byKey(const Key('home-feed-title-anime-card')))
.maxLines,
2,
);
expect(find.byKey(const Key('home-feed-tag-anime-card-0')), findsOneWidget);
expect(find.byKey(const Key('home-feed-tag-anime-card-1')), findsOneWidget);
expect(find.text('Romance'), findsOneWidget);
expect(find.text('Urban'), findsOneWidget);
await tester.tap(card);
expect(taps, 1);
});
testWidgets('should_choose_anime_new_and_hot_badges_from_real_data', (
tester,
) async {
final now = DateTime(2026, 10, 22, 12);
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
theme: AppTheme.dark(),
locale: const Locale('en'),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Scaffold(
body: SingleChildScrollView(
child: Builder(
builder: (context) => SizedBox(
width: 180,
child: HomeDramaCard(
l10n: AppLocalizations.of(context)!,
drama: _drama,
onTap: () {},
body: Row(
children: [
for (final drama in [
_drama(id: 'anime', title: 'Anime', dramaType: 'ANIME'),
_drama(
id: 'new',
title: 'New',
createdAt: now.subtract(const Duration(days: 2)),
),
),
),
_drama(id: 'hot', title: 'Hot', isHot: true),
])
SizedBox(
width: 130,
height: 240,
child: HomeFeedCard(drama: drama, onTap: () {}, now: now),
),
],
),
),
),
);
await tester.pumpAndSettle();
final card = find.byKey(const Key('home-drama-card-style-drama'));
final cardContainer = tester.widget<Container>(
find.ancestor(of: card, matching: find.byType(Container)).last,
);
final cardDecoration = cardContainer.decoration! as BoxDecoration;
expect(cardDecoration.color, AppColors.card);
expect(
cardDecoration.borderRadius,
BorderRadius.circular(AppDimens.cardRadius),
);
expect(cardDecoration.boxShadow, const [AppTheme.cardShadow]);
final tag = tester.widget<Container>(
find.byKey(const Key('home-drama-tag-style-drama')),
);
expect((tag.decoration! as BoxDecoration).color, AppColors.badgePink);
expect(find.text('Free'), findsOneWidget);
expect(find.text('All 24'), findsOneWidget);
expect(
find.byKey(const Key('home-drama-locked-style-drama')),
findsNothing,
);
expect(find.byKey(const Key('home-drama-add-style-drama')), findsNothing);
// 播放次数角标落在封面内,并按万位格式化。
final views = find.byKey(const Key('home-drama-views-style-drama'));
expect(
find.descendant(
of: find.byKey(const Key('home-drama-cover-style-drama')),
matching: views,
),
find.byKey(const Key('home-feed-badge-anime-anime')),
findsOneWidget,
);
expect(
find.descendant(of: views, matching: find.text('1.2万')),
find.byKey(const Key('home-feed-badge-newRelease-new')),
findsOneWidget,
);
// 压缩后卡片高度接近「卡宽 × 1.5」,明显矮于旧版 3:4 封面。
final cardSize = tester.getSize(card);
expect(cardSize.height, lessThan(cardSize.width * 1.6));
final coverSize = tester.getSize(
find.byKey(const Key('home-drama-cover-style-drama')),
);
expect(coverSize.height / coverSize.width, closeTo(6 / 5, 0.02));
expect(find.byKey(const Key('home-feed-badge-hot-hot')), findsOneWidget);
});
}
const _drama = Drama(
id: 'style-drama',
title: 'Warm Orange Story',
synopsis: '暖橙短剧',
posterUrl: '/files/style.jpg',
genre: 'Romance',
totalEpisodes: 24,
freeEpisodes: 6,
viewCount: 12400,
);
Drama _drama({
required String id,
required String title,
String dramaType = 'LIVE',
int viewCount = 0,
bool isHot = false,
DateTime? createdAt,
}) {
return Drama(
id: id,
title: title,
synopsis: 'synopsis',
posterUrl: '/files/$id.jpg',
genre: 'Romance',
category: 'Urban',
viewCount: viewCount,
isHot: isHot,
dramaType: dramaType,
createdAt: createdAt,
);
}
Future<void> _pumpCard(
WidgetTester tester, {
required Drama drama,
required VoidCallback onTap,
}) {
return tester.pumpWidget(
MaterialApp(
theme: AppTheme.dark(),
locale: const Locale('en'),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Scaffold(
body: Center(
child: SizedBox(
width: 130,
height: 240,
child: HomeFeedCard(
drama: drama,
onTap: onTap,
now: DateTime(2026, 10, 22, 12),
),
),
),
),
),
);
}
+252
View File
@@ -0,0 +1,252 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.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/feed_page.dart';
import 'package:fruitshorts/core/data/models/home_feed.dart';
import 'package:fruitshorts/core/data/repositories/catalog_repository.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/pages/player/player_view.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 '../support/fake_category.dart';
import '../support/fake_haptics.dart';
import '../support/fake_interaction_repositories.dart';
import '../support/widget_player.dart';
const _categories = [
ContentCategory(id: 'costume', name: '古装'),
ContentCategory(id: 'community', name: '社群'),
ContentCategory(id: 'uplifting', name: '爽剧'),
ContentCategory(id: 'sweet', name: '甜宠'),
ContentCategory(id: 'face-slap', name: '打脸虐渣'),
ContentCategory(id: 'angst', name: '虐恋'),
];
void main() {
setUp(() {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_render_primary_sections_and_secondary_tags_in_order', (
tester,
) async {
final repository = _PagedCatalogRepository();
await _pumpApp(tester, repository);
final sectionKeys = [
const Key('home-section-featured'),
const Key('home-section-new'),
const Key('home-section-categories'),
const Key('home-section-ranking'),
const Key('home-section-category-古装'),
const Key('home-section-category-社群'),
];
final sectionCenters = sectionKeys
.map((key) => tester.getCenter(find.byKey(key)).dx)
.toList();
expect(sectionCenters, orderedEquals(sectionCenters.toList()..sort()));
final tagKeys = [
const Key('home-tag-hot'),
const Key('home-tag-uplifting'),
const Key('home-tag-sweet'),
const Key('home-tag-faceSlap'),
const Key('home-tag-angst'),
];
final tagCenters = tagKeys
.map((key) => tester.getCenter(find.byKey(key)).dx)
.toList();
expect(tagCenters, orderedEquals(tagCenters.toList()..sort()));
expect(find.text('Featured'), findsOneWidget);
expect(find.text('New'), findsOneWidget);
expect(find.text('Categories'), findsOneWidget);
expect(find.text('Charts'), findsOneWidget);
expect(find.text('Costume'), findsOneWidget);
expect(find.text('Community'), findsOneWidget);
});
testWidgets('should_hide_unmatched_content_sections', (tester) async {
final repository = _PagedCatalogRepository();
await _pumpApp(
tester,
repository,
categories: const [ContentCategory(id: 'romance', name: 'Romance')],
);
expect(find.byKey(const Key('home-section-category-古装')), findsNothing);
expect(find.byKey(const Key('home-section-category-社群')), findsNothing);
expect(find.byKey(const Key('home-tag-uplifting')), findsNothing);
});
testWidgets('should_open_existing_pages_from_home_sections', (tester) async {
final repository = _PagedCatalogRepository();
await _pumpApp(tester, repository);
await tester.tap(find.byKey(const Key('home-section-new')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('new-releases-page')), findsOneWidget);
await tester.pageBack();
await tester.pumpAndSettle();
await tester.ensureVisible(find.byKey(const Key('home-section-ranking')));
await tester.tap(find.byKey(const Key('home-section-ranking')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('ranking-page')), findsOneWidget);
await tester.pageBack();
await tester.pumpAndSettle();
await tester.ensureVisible(
find.byKey(const Key('home-section-categories')),
);
await tester.tap(find.byKey(const Key('home-section-categories')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('filter-page')), findsOneWidget);
});
testWidgets('should_load_next_page_without_expanding_page_size', (
tester,
) async {
final repository = _PagedCatalogRepository();
await _pumpApp(tester, repository);
expect(find.byKey(const Key('home-feed-card-page1-0')), findsOneWidget);
await tester.drag(
find.byKey(const Key('home-grid-scroll')),
const Offset(0, -1200),
);
await tester.pumpAndSettle();
expect(repository.cursors, contains('page1'));
expect(repository.requestedSizes.every((size) => size <= 10), isTrue);
await tester.scrollUntilVisible(
find.byKey(const Key('home-feed-card-page2-1')),
260,
scrollable: find
.descendant(
of: find.byKey(const Key('home-grid-refresh')),
matching: find.byType(Scrollable),
)
.first,
);
expect(find.byKey(const Key('home-feed-card-page2-1')), findsOneWidget);
});
testWidgets('should_open_player_when_grid_card_is_tapped', (tester) async {
final repository = _PagedCatalogRepository();
await _pumpApp(tester, repository);
await tester.tap(find.byKey(const Key('home-feed-card-page1-0')));
await tester.pumpAndSettle();
final player = tester.widget<PlayerPage>(find.byType(PlayerPage).last);
expect(player.homeMode, isFalse);
});
}
Future<void> _pumpApp(
WidgetTester tester,
_PagedCatalogRepository repository, {
List<ContentCategory> categories = _categories,
}) async {
tester.view.physicalSize = const Size(390, 844);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(
LumaDramaApp(
playerFactory: createWidgetPlayer,
catalogRepository: repository,
categoryRepository: FakeCategoryRepository(
catalog: repository,
categories: categories,
),
watchHistoryRepository: InMemoryWatchHistoryRepository(),
favoriteRepository: InMemoryFavoriteRepository(
cards: repository.allDramas,
),
playbackRepository: FakePlaybackRepository(),
commentRepository: FakeCommentRepository(),
interactionRepository: FakeInteractionRepository(),
haptics: FakeHaptics(),
),
);
await tester.pumpAndSettle();
}
class _PagedCatalogRepository implements CatalogRepository {
_PagedCatalogRepository()
: pageOne = List.generate(10, (index) => _drama('page1-$index')),
pageTwo = List.generate(2, (index) => _drama('page2-$index'));
final List<Drama> pageOne;
final List<Drama> pageTwo;
final List<String?> cursors = [];
final List<int> requestedSizes = [];
List<Drama> get allDramas => [...pageOne, ...pageTwo];
@override
Future<HomeFeed> fetchHomeFeed() async => _feed(pageOne);
@override
Future<HomeFeed> fetchDiscoverFeed() async => _feed(pageOne);
@override
Future<FeedPage> fetchFeed({
List<String> types = const [],
String? categoryId,
String? cursor,
int size = 10,
}) async {
requestedSizes.add(size);
if (types.isNotEmpty) {
return const FeedPage(dramas: []);
}
cursors.add(cursor);
if (cursor == 'page1') {
return FeedPage(dramas: pageTwo, hasMore: false);
}
return FeedPage(dramas: pageOne, nextCursor: 'page1', hasMore: true);
}
@override
Future<Drama> fetchDrama(String dramaId) async {
return allDramas.firstWhere((drama) => drama.id == dramaId);
}
HomeFeed _feed(List<Drama> dramas) {
return HomeFeed(
sections: [FeedSection(code: 'hot', title: 'Hot', dramas: dramas)],
);
}
}
Drama _drama(String id) {
return Drama(
id: id,
title: 'Grid Drama $id',
synopsis: 'synopsis',
posterUrl: '',
genre: 'Romance',
category: 'Urban',
viewCount: 200000,
detailLoaded: true,
episodes: [
Episode(
id: '$id-1',
number: 1,
title: 'Episode 1',
isFree: true,
videoUrl: 'https://example.test/$id.mp4',
),
],
);
}
+3 -3
View File
@@ -209,11 +209,11 @@ void main() {
expect(find.byKey(const Key('tab-follow')), findsOneWidget);
expect(find.byKey(const Key('tab-mine')), findsOneWidget);
expect(find.byKey(const Key('tab-create')), findsNothing);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
@@ -251,7 +251,7 @@ void main() {
await tester.tap(find.byKey(const Key('tab-home')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
});
testWidgets('should_not_haptic_when_ordinary_list_item_is_opened', (
+61 -6
View File
@@ -9,6 +9,7 @@ import 'package:shared_preferences_platform_interface/in_memory_shared_preferenc
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';
@@ -20,6 +21,7 @@ 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';
@@ -30,7 +32,9 @@ import '../support/fake_notice_repository.dart';
import '../support/widget_player.dart';
void main() {
testWidgets('should_render_immersive_feed_on_home', (tester) async {
testWidgets('should_render_three_column_explorer_grid_on_home', (
tester,
) async {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.empty();
tester.view.physicalSize = const Size(390, 844);
@@ -48,6 +52,58 @@ void main() {
),
],
);
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, 8);
expect(delegate.mainAxisSpacing, 8);
expect(find.byKey(const Key('home-section-featured')), findsOneWidget);
expect(find.byKey(const Key('home-section-new')), findsOneWidget);
expect(find.byKey(const Key('home-section-categories')), findsOneWidget);
expect(find.byKey(const Key('home-section-ranking')), findsOneWidget);
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')), findsOneWidget);
expect(find.byKey(const Key('home-grid-filter')), findsOneWidget);
expect(find.byType(PageView), findsNothing);
expect(tester.takeException(), isNull);
});
testWidgets('should_keep_immersive_player_on_recommend_tab', (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,
@@ -63,14 +119,13 @@ void main() {
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-home')));
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-feed-full-series')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsNothing);
expect(find.byType(PageView), findsOneWidget);
expect(find.byKey(const Key('home-browse-page')), findsNothing);
expect(tester.takeException(), isNull);
expect(tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode, isTrue);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
});
testWidgets('should_render_new_feed_structure_with_formatted_counts', (
+1 -1
View File
@@ -4,7 +4,7 @@ import 'package:shared_preferences_platform_interface/in_memory_shared_preferenc
import 'package:shared_preferences_platform_interface/shared_preferences_async_platform_interface.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/home/home_view.dart';
import 'package:fruitshorts/pages/home/widgets/home_failure_view.dart';
void main() {
setUp(() {
+1 -1
View File
@@ -123,7 +123,7 @@ void main() {
await tester.pumpAndSettle();
expect(find.byKey(const Key('startup-splash')), findsNothing);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
});
}
+40 -82
View File
@@ -46,36 +46,18 @@ void main() {
expect(find.byType(AppBottomNav), findsOneWidget);
});
testWidgets('should_start_home_on_immersive_feed', (tester) async {
testWidgets('should_start_home_on_explorer_grid', (tester) async {
await _pumpApp(tester);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-feed-full-series')), findsOneWidget);
expect(find.byKey(const Key('home-feed-black-background')), findsOneWidget);
expect(find.byType(PageView), findsOneWidget);
expect(find.byKey(const Key('home-browse-page')), findsNothing);
expect(
tester.widget<Text>(find.byKey(const Key('rail-like-count'))).data,
'41.9万',
);
expect(
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
'1006',
);
expect(
tester.widget<Text>(find.byKey(const Key('rail-comments-count'))).data,
'3.2万',
);
expect(find.byKey(const Key('home-feed-refresh')), findsOneWidget);
expect(find.byKey(const Key('home-feed-danmaku')), findsNothing);
expect(find.byKey(const Key('home-feed-fullscreen')), findsNothing);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
expect(find.byKey(const Key('home-grid-cards')), findsOneWidget);
expect(find.byType(PageView), findsNothing);
expect(
tester.widget<Scaffold>(find.byType(Scaffold).first).backgroundColor,
Colors.transparent,
);
expect(find.byKey(const Key('home-page-gradient')), findsNothing);
expect(_hasLightStatusBar(tester), isTrue);
expect(tester.widget<PlayerPage>(find.byType(PlayerPage)).homeMode, isTrue);
});
testWidgets('should_keep_home_feed_size_when_keyboard_insets_change', (
@@ -83,7 +65,7 @@ void main() {
) async {
addTearDown(tester.view.resetViewInsets);
await _pumpApp(tester);
final feed = find.byKey(const Key('home-feed-black-background'));
final feed = find.byKey(const Key('home-grid'));
final before = tester.getRect(feed);
tester.view.viewInsets = const FakeViewPadding(bottom: 300);
@@ -99,7 +81,7 @@ void main() {
});
testWidgets('should_open_full_player_from_full_series_bar', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle();
@@ -109,7 +91,7 @@ void main() {
});
testWidgets('should_switch_to_anime_channel', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
await tester.tap(find.byKey(const Key('home-feed-tab-anime')));
await tester.pumpAndSettle();
@@ -126,7 +108,7 @@ void main() {
tester,
) async {
final catalog = _PendingAnimeCatalog();
await _pumpApp(tester, catalog: catalog);
await _pumpApp(tester, catalog: catalog, recommend: true);
for (var attempt = 0; attempt < 5 && catalog.animeCalls == 0; attempt++) {
await tester.pump();
}
@@ -151,7 +133,7 @@ void main() {
testWidgets('should_show_empty_state_when_anime_has_no_dramas', (
tester,
) async {
await _pumpApp(tester, catalog: _EmptyAnimeCatalog());
await _pumpApp(tester, catalog: _EmptyAnimeCatalog(), recommend: true);
await tester.tap(find.byKey(const Key('home-feed-tab-anime')));
await tester.pumpAndSettle();
@@ -161,7 +143,7 @@ void main() {
});
testWidgets('should_refresh_feed_without_blank_loading', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
await tester.tap(find.byKey(const Key('home-feed-refresh')));
await tester.pump();
@@ -173,7 +155,7 @@ void main() {
});
testWidgets('should_keep_feed_rail_above_bottom_panels', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final railTop = tester.getCenter(find.byKey(const Key('rail-save'))).dy;
final railBottom = tester
.getRect(find.byKey(const Key('rail-share')))
@@ -194,7 +176,7 @@ void main() {
testWidgets('should_keep_feed_header_and_progress_after_clear', (
tester,
) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final gesture = await tester.startGesture(const Offset(195, 300));
await tester.pump(const Duration(milliseconds: 700));
await tester.pump();
@@ -223,7 +205,7 @@ void main() {
});
testWidgets('should_boost_feed_on_side_long_press', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
final gesture = await tester.startGesture(const Offset(60, 300));
@@ -245,7 +227,7 @@ void main() {
});
testWidgets('should_hide_exit_clear_button_while_boosting', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final moreGesture = await tester.startGesture(const Offset(195, 300));
await tester.pump(const Duration(milliseconds: 700));
await tester.pump();
@@ -282,7 +264,7 @@ void main() {
testWidgets('should_resume_playback_when_boosting_while_paused', (
tester,
) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
await tester.tap(find.byKey(const Key('home-feed-video-toggle')));
await tester.pump();
expect(find.byKey(const Key('home-feed-center-play')), findsOneWidget);
@@ -302,7 +284,7 @@ void main() {
});
testWidgets('should_open_more_sheet_on_center_long_press', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final gesture = await tester.startGesture(const Offset(195, 300));
await tester.pump(const Duration(milliseconds: 700));
@@ -316,7 +298,7 @@ void main() {
expect(find.byKey(const Key('home-feed-pip')), findsNothing);
});
testWidgets('should_keep_light_status_bar_after_leaving_feed_tab', (
testWidgets('should_keep_light_status_bar_in_grid_and_recommend', (
tester,
) async {
await _pumpApp(tester);
@@ -325,12 +307,13 @@ void main() {
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-page-gradient')), findsOneWidget);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-page-gradient')), findsNothing);
expect(_hasLightStatusBar(tester), isTrue);
});
testWidgets('should_switch_feed_drama_when_swiped_up', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final before = tester
.widget<Text>(find.byKey(const Key('home-feed-title')))
.data;
@@ -345,7 +328,7 @@ void main() {
});
testWidgets('should_keep_feed_header_fixed_when_swiping', (tester) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
final before = tester.getRect(find.byKey(const Key('home-feed-search')));
await tester.drag(find.byType(PageView).first, const Offset(0, -600));
@@ -360,7 +343,7 @@ void main() {
testWidgets('should_resume_feed_after_returning_from_full_player', (
tester,
) async {
await _pumpApp(tester);
await _pumpApp(tester, recommend: true);
await tester.tap(find.byKey(const Key('home-feed-video-toggle')));
await tester.pump();
expect(find.byKey(const Key('home-feed-center-play')), findsOneWidget);
@@ -379,23 +362,21 @@ void main() {
expect(find.byKey(const Key('home-feed-center-play')), findsNothing);
});
testWidgets('should_hide_feed_when_switching_to_theater', (tester) async {
testWidgets('should_switch_from_grid_to_recommend_player', (tester) async {
await _pumpApp(tester);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsNothing);
expect(find.byKey(const Key('discover-search')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsNothing);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
});
testWidgets('should_open_theater_search_when_feed_search_is_tapped', (
tester,
) async {
testWidgets('should_open_discover_search_from_home_search', (tester) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('home-feed-search')));
await tester.tap(find.byKey(const Key('home-search-entry')));
await tester.pumpAndSettle();
final field = tester.widget<TextField>(
@@ -404,41 +385,12 @@ void main() {
expect(field.focusNode?.hasFocus, isTrue);
});
testWidgets('should_not_render_theater_language_filters', (tester) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('theater-language-chips')), findsNothing);
expect(find.byKey(const Key('theater-category-chips')), findsOneWidget);
expect(find.byKey(const Key('discover-search')), findsOneWidget);
expect(find.byKey(const Key('discover-refresh')), findsOneWidget);
expect(
find.byKey(const Key('home-drama-card-the-signal-demo')),
findsOneWidget,
);
});
testWidgets('should_open_fullscreen_player_when_theater_card_is_tapped', (
testWidgets('should_open_player_when_home_grid_card_is_tapped', (
tester,
) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
final card = find.byKey(const Key('home-drama-card-the-signal-demo'));
await tester.scrollUntilVisible(
card,
220,
scrollable: find
.descendant(
of: find.byKey(const Key('discover-refresh')),
matching: find.byType(Scrollable),
)
.first,
);
final card = find.byKey(const Key('home-feed-card-the-signal-demo'));
await tester.ensureVisible(card);
await tester.pumpAndSettle();
await tester.tap(card);
await tester.pumpAndSettle();
@@ -446,16 +398,17 @@ void main() {
expect(player.homeMode, isFalse);
});
testWidgets('should_fit_home_feed_and_theater_on_320_width', (tester) async {
testWidgets('should_fit_home_grid_and_recommend_on_320_width', (
tester,
) async {
await _pumpApp(tester, size: const Size(320, 568));
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid-cards')), findsOneWidget);
expect(tester.takeException(), isNull);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('theater-category-chips')), findsOneWidget);
expect(find.byKey(const Key('theater-language-chips')), findsNothing);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(tester.takeException(), isNull);
});
}
@@ -477,6 +430,7 @@ Future<void> _pumpApp(
WidgetTester tester, {
Size size = const Size(390, 844),
FakeCatalogRepository? catalog,
bool recommend = false,
}) async {
tester.view.physicalSize = size;
tester.view.devicePixelRatio = 1;
@@ -497,6 +451,10 @@ Future<void> _pumpApp(
),
);
await tester.pumpAndSettle();
if (recommend) {
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
}
}
class _PendingAnimeCatalog extends FakeCatalogRepository {
+17 -7
View File
@@ -35,13 +35,13 @@ void main() {
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_open_fullscreen_overlay_from_discover_card', (
testWidgets('should_open_fullscreen_overlay_from_recommend_feed', (
tester,
) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('home-drama-card-city-lights-demo')));
await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle();
_expectFullscreenOverlay(tester);
@@ -59,6 +59,8 @@ void main() {
tester,
) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
_expectPlayerNavigationBar(tester);
await _openSeriesPlayer(tester);
@@ -78,6 +80,8 @@ void main() {
);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pump();
final ticketLoading = find.byKey(const Key('player-ticket-loading'));
expect(ticketLoading, findsOneWidget);
@@ -160,6 +164,8 @@ void main() {
return player;
},
);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
final player = players.firstWhere((item) => item.playing);
expect(player.playing, isTrue);
@@ -215,6 +221,8 @@ void main() {
});
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('rail-comments')));
await tester.pumpAndSettle();
@@ -269,6 +277,8 @@ void main() {
});
final catalog = FakeCatalogRepository(dramas: [_countingDrama]);
await _pumpApp(tester, catalog: catalog);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
@@ -865,7 +875,7 @@ void main() {
);
await _openSeriesPlayer(
tester,
cardKey: const Key('home-drama-card-long-copy-demo'),
cardKey: const Key('home-feed-card-long-copy-demo'),
);
expect(tester.takeException(), isNull);
@@ -1004,7 +1014,7 @@ void main() {
);
await _openSeriesPlayer(
tester,
cardKey: const Key('home-drama-card-locked-demo'),
cardKey: const Key('home-feed-card-locked-demo'),
);
expect(find.byKey(const Key('progress-loading')), findsNothing);
expect(find.byKey(const Key('progress-loading-highlight')), findsNothing);
@@ -1329,9 +1339,9 @@ Future<void> _clearFromMore(WidgetTester tester) async {
Future<void> _openSeriesPlayer(
WidgetTester tester, {
Key cardKey = const Key('home-drama-card-city-lights-demo'),
Key cardKey = const Key('home-feed-card-city-lights-demo'),
}) async {
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.tap(find.byKey(const Key('tab-home')));
await tester.pumpAndSettle();
final card = find.byKey(cardKey);
await tester.scrollUntilVisible(
@@ -1339,7 +1349,7 @@ Future<void> _openSeriesPlayer(
220,
scrollable: find
.descendant(
of: find.byKey(const Key('discover-refresh')),
of: find.byKey(const Key('home-grid-refresh')),
matching: find.byType(Scrollable),
)
.first,
+24 -40
View File
@@ -66,9 +66,10 @@ void main() {
await tester.pumpAndSettle();
// 首页 feed 启动会创建首个播放器,先清掉再统计剧场播放器。
created.clear();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
expect(created, hasLength(2));
expect(created, isNotEmpty);
final createdBeforeSwipe = created.length;
expect(find.byType(CircularProgressIndicator), findsNothing);
await tester.drag(find.byType(PageView).last, const Offset(0, -550));
await tester.pumpAndSettle();
@@ -77,8 +78,8 @@ void main() {
tester.widget<Text>(find.byKey(const Key('player-episode-heading'))).data,
contains('Episode 2'),
);
expect(created, hasLength(3));
expect(created.toSet(), hasLength(3));
expect(created.length, greaterThan(createdBeforeSwipe));
expect(created, contains('the-signal-demo-2'));
});
testWidgets('should_open_fullscreen_player_when_theater_card_is_tapped', (
@@ -86,7 +87,7 @@ void main() {
) async {
await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
final player = tester.widget<PlayerPage>(find.byType(PlayerPage).last);
expect(player.homeMode, isFalse);
@@ -103,22 +104,22 @@ void main() {
await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
expect(find.byType(PlayerPage), findsOneWidget);
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget);
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(
Navigator.of(
tester.element(find.byKey(const Key('home-all-dramas-grid'))),
tester.element(find.byKey(const Key('home-feed-title'))),
).canPop(),
isFalse,
);
await tester.binding.handlePopRoute();
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-feed-title')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
await tester.tap(find.byKey(const Key('tab-follow')));
await tester.pumpAndSettle();
@@ -183,7 +184,7 @@ void main() {
await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
expect(find.byKey(const Key('progress-loading')), findsNothing);
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
@@ -197,9 +198,7 @@ void main() {
expect(tester.takeException(), isNull);
});
testWidgets('should_render_theater_filters_and_grid_on_same_route_tab', (
tester,
) async {
testWidgets('should_render_home_explorer_grid_on_home_tab', (tester) async {
tester.view.physicalSize = const Size(360, 720);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
@@ -207,13 +206,11 @@ void main() {
await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('discover-search')), findsOneWidget);
expect(find.byKey(const Key('theater-category-chips')), findsOneWidget);
expect(find.byKey(const Key('theater-language-chips')), findsNothing);
expect(find.byKey(const Key('home-all-dramas-grid')), findsOneWidget);
expect(find.byKey(const Key('home-grid')), findsOneWidget);
expect(find.byKey(const Key('home-grid-cards')), findsOneWidget);
expect(find.byKey(const Key('home-search-entry')), findsOneWidget);
expect(find.byKey(const Key('home-secondary-tags')), findsOneWidget);
expect(tester.takeException(), isNull);
});
@@ -244,7 +241,7 @@ void main() {
),
);
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
await tester.tap(find.byKey(const Key('player-rail-comments')));
await tester.pumpAndSettle();
@@ -264,13 +261,13 @@ void main() {
await tester.tap(find.byKey(const Key('player-rail-like')));
await tester.pumpAndSettle();
expect(interactions.likeTargets, contains('EPISODE:city-lights-demo-1'));
expect(interactions.likeTargets, contains('EPISODE:the-signal-demo-1'));
await tester.tap(find.byKey(const Key('player-rail-save')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('player-rail-share')));
await tester.pumpAndSettle();
expect(sharedText, contains('City Lights & Velvet Nights'));
expect(sharedText, contains('The Signal'));
expect(sharedText, contains('FruitShorts'));
expect(tester.takeException(), isNull);
});
@@ -284,7 +281,7 @@ void main() {
await tester.pumpWidget(appUnderTest(interactionRepository: interactions));
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
await tester.tap(find.byKey(const Key('player-rail-like')));
await tester.pumpAndSettle();
@@ -300,7 +297,7 @@ void main() {
await tester.pumpWidget(appUnderTest());
await tester.pumpAndSettle();
await _openTheaterPlayer(tester);
await _openRecommendFullPlayer(tester);
await tester.tap(find.byKey(const Key('player-rail-save')));
await tester.pumpAndSettle();
@@ -311,26 +308,13 @@ void main() {
await tester.tap(find.byKey(const Key('mine-watchlist')));
await tester.pumpAndSettle();
expect(find.text('City Lights & Velvet Nights'), findsWidgets);
expect(find.text('The Signal'), findsWidgets);
});
}
Future<void> _openTheaterPlayer(WidgetTester tester) async {
Future<void> _openRecommendFullPlayer(WidgetTester tester) async {
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
final card = find.byKey(const Key('home-drama-card-city-lights-demo'));
await tester.scrollUntilVisible(
card,
220,
scrollable: find
.descendant(
of: find.byKey(const Key('discover-refresh')),
matching: find.byType(Scrollable),
)
.first,
);
await tester.ensureVisible(card);
await tester.pumpAndSettle();
await tester.tap(card);
await tester.tap(find.byKey(const Key('home-feed-full-series')));
await tester.pumpAndSettle();
}