feat(recommend): 上新板块改按后端语义码判断,顶部轮播改为无缝循环
flutter-ci / test (push) Failing after 30m39s

Drama 新增 tagCode,推荐页只看 NEW_ARRIVAL 这个码决定上新横滑,
不再依赖可变的标签文案;分类网格高度改为复用卡片的统一常量。
头部轮播改用虚拟无限页码,首尾相接且能反向滑,自动播放不再倒滑回第一张。
This commit is contained in:
Vicky authored and trae-solo-agent committed 2026-10-29 15:44:33 +08:00
1 parent c311985601
commit 3590cd0c55
5 files changed
+235 -73

No files matched your search

@@ -0,0 +1,83 @@
// Author: fruit.com.cn
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/models/home_feed.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/recommend/widgets/home_hero_carousel.dart';
const List<FeedBanner> _banners = [
FeedBanner(id: '1', imageUrl: 'https://cdn.test/a.png'),
FeedBanner(id: '2', imageUrl: 'https://cdn.test/b.png'),
];
Widget _harness(List<FeedBanner> banners) {
return MaterialApp(
locale: const Locale('en'),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: AppLocalizations.localizationsDelegates,
home: Scaffold(
body: HomeHeroCarousel(
banners: banners,
dramaFor: (_) => null,
onWatch: (_) {},
),
),
);
}
Color? _dotColor(WidgetTester tester, int index) {
final decoration =
tester
.widget<Container>(find.byKey(Key('home-hero-dot-$index')))
.decoration
as BoxDecoration?;
return decoration?.color;
}
void main() {
testWidgets('should_keep_looping_backwards_from_the_first_banner', (
tester,
) async {
await tester.pumpWidget(_harness(_banners));
await tester.pumpAndSettle();
expect(_dotColor(tester, 0), AppColors.brandRed);
// 从第一张往右滑:无缝轮播应该接到最后一张,而不是被边界挡住。
await tester.fling(find.byType(PageView), const Offset(700, 0), 1200);
await tester.pumpAndSettle();
expect(_dotColor(tester, 1), AppColors.brandRed);
expect(_dotColor(tester, 0), isNot(AppColors.brandRed));
});
testWidgets('should_wrap_to_first_banner_after_last_auto_advance', (
tester,
) async {
await tester.pumpWidget(_harness(_banners));
await tester.pumpAndSettle();
// 自动播两次:第 2 张之后应回到第 1 张,且页码继续往前走不回头。
await tester.pump(const Duration(milliseconds: 4600));
await tester.pumpAndSettle();
expect(_dotColor(tester, 1), AppColors.brandRed);
await tester.pump(const Duration(milliseconds: 4600));
await tester.pumpAndSettle();
expect(_dotColor(tester, 0), AppColors.brandRed);
});
testWidgets('should_not_loop_when_only_one_banner', (tester) async {
await tester.pumpWidget(
_harness(const [FeedBanner(id: '1', imageUrl: 'https://cdn.test/a.png')]),
);
await tester.pumpAndSettle();
expect(find.byKey(const Key('home-hero-dot-0')), findsNothing);
await tester.pump(const Duration(milliseconds: 6000));
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
});
}
+57 -9
View File
@@ -3,6 +3,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/app.dart';
import 'package:fruitshorts/core/data/models/drama.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';
@@ -10,6 +11,7 @@ import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_dimens.dart';
import 'package:fruitshorts/core/widgets/app_bottom_nav.dart';
import 'package:fruitshorts/pages/player/player_view.dart';
import 'package:fruitshorts/pages/recommend/recommend_discover_page.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';
@@ -34,7 +36,10 @@ void main() {
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
expect(find.byKey(const Key('recommend-tab-playlist')), findsOneWidget);
expect(find.byKey(const Key('recommend-tab-all')), findsOneWidget);
expect(find.byKey(const Key('recommend-feed-player'), skipOffstage: false), findsOneWidget);
expect(
find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
findsOneWidget,
);
expect(find.byKey(const Key('recommend-top-gradient')), findsOneWidget);
final recommendText = tester.widget<Text>(
find.descendant(
@@ -65,7 +70,9 @@ void main() {
find.byKey(const Key('recommend-top-nav')),
);
// 右侧依次是搜索和金色礼物盒(钱包入口),礼物盒贴右边距 8。
final searchRect = tester.getRect(find.byKey(const Key('recommend-search')));
final searchRect = tester.getRect(
find.byKey(const Key('recommend-search')),
);
final giftRect = tester.getRect(find.byKey(const Key('recommend-gift')));
expect(giftRect.right, closeTo(topNavRect.right - 8, 0.1));
expect(searchRect.right, lessThanOrEqualTo(giftRect.left));
@@ -78,7 +85,12 @@ void main() {
tester
.getRect(find.byKey(const Key('recommend-top-nav')))
.overlaps(
tester.getRect(find.byKey(const Key('recommend-feed-player'), skipOffstage: false)),
tester.getRect(
find.byKey(
const Key('recommend-feed-player'),
skipOffstage: false,
),
),
),
isTrue,
);
@@ -387,7 +399,10 @@ void main() {
tester.getRect(find.byKey(const Key('all-dramas-filters'))).top,
closeTo(fixedFilterTop, 0.1),
);
expect(find.byKey(const Key('all-filter-classification-all')), findsOneWidget);
expect(
find.byKey(const Key('all-filter-classification-all')),
findsOneWidget,
);
expect(find.byKey(const Key('all-filter-category-all')), findsOneWidget);
expect(find.byKey(const Key('all-filter-tag-all')), findsOneWidget);
expect(
@@ -407,7 +422,10 @@ void main() {
await tester.tap(find.byKey(const Key('recommend-tab-playlist')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
expect(find.byKey(const Key('recommend-feed-player'), skipOffstage: false), findsOneWidget);
expect(
find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
findsOneWidget,
);
expect(find.byType(AppBottomNav), findsOneWidget);
await tester.ensureVisible(find.byKey(const Key('recommend-tab-all')));
@@ -430,7 +448,10 @@ void main() {
await tester.drag(pages, const Offset(-320, 0));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-section-hits')), findsOneWidget);
expect(find.byKey(const Key('recommend-feed-player'), skipOffstage: false), findsOneWidget);
expect(
find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
findsOneWidget,
);
expect(
tester
.widget<Text>(
@@ -454,7 +475,10 @@ void main() {
await tester.drag(pages, const Offset(640, 0));
await tester.pumpAndSettle();
expect(find.byKey(const Key('recommend-feed-player'), skipOffstage: false), findsOneWidget);
expect(
find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
findsOneWidget,
);
expect(find.byKey(const Key('recommend-tab-recommend')), findsOneWidget);
_drainOverflows(tester);
});
@@ -497,7 +521,9 @@ void main() {
await _pumpRecommend(tester);
final gesture = await tester.startGesture(
tester.getCenter(find.byKey(const Key('recommend-feed-player'), skipOffstage: false)),
tester.getCenter(
find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
),
);
await gesture.moveBy(const Offset(0, 80));
await tester.pump();
@@ -505,7 +531,10 @@ void main() {
final feedScroll = tester.state<ScrollableState>(
find
.descendant(
of: find.byKey(const Key('recommend-feed-player'), skipOffstage: false),
of: find.byKey(
const Key('recommend-feed-player'),
skipOffstage: false,
),
matching: find.byType(Scrollable),
)
.first,
@@ -544,6 +573,25 @@ void main() {
expect(search.focusNode?.hasFocus, isTrue);
_drainOverflows(tester);
});
test('should_judge_new_arrival_only_by_tag_code', () {
Drama tagged({String tagCode = '', String tag = ''}) => Drama(
id: 'tagged',
title: 'Tagged',
synopsis: 'synopsis',
posterUrl: '',
tag: tag,
tagCode: tagCode,
);
expect(isNewArrivalTag(tagged(tagCode: 'NEW_ARRIVAL')), isTrue);
expect(isNewArrivalTag(tagged(tagCode: ' new_arrival ')), isTrue);
// 其它语义码(以及没有码的标签)都不算上新,文案不参与判断。
expect(isNewArrivalTag(tagged(tagCode: 'HOT')), isFalse);
expect(isNewArrivalTag(tagged(tagCode: 'NEW_RELEASE')), isFalse);
expect(isNewArrivalTag(tagged(tag: '上新')), isFalse);
expect(isNewArrivalTag(tagged()), isFalse);
});
}
/// 测试字体(等宽方块字形)比真实字体宽/高,卡片固定高度会报 2~4px