feat(home): 顶部导航随翻页联动并把推荐更名为精选
This commit is contained in:
1 parent
2f4593a46e
commit
335e5dc286
27 files changed
+1095
-214
No files matched your search
@@ -0,0 +1,73 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:验证推荐页封面标签使用常规字重,保留原有样式与点击。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/recommend/widgets/home_recommend_content.dart';
|
||||
|
||||
const _tag = '热播';
|
||||
const _fallback = '上新';
|
||||
const _posterWidth = 110.0;
|
||||
const _posterHeight = 230.0;
|
||||
|
||||
void main() {
|
||||
for (final tag in [_tag, '']) {
|
||||
testWidgets(
|
||||
'should_keep_badge_regular_when_using_${tag.isEmpty ? 'fallback' : 'tag'}',
|
||||
(tester) async {
|
||||
var taps = 0;
|
||||
await tester.pumpWidget(_host(tag, () => taps++));
|
||||
await tester.pumpAndSettle();
|
||||
final label = find.text(tag.isEmpty ? _fallback : tag);
|
||||
final text = tester.widget<Text>(label);
|
||||
expect(text.style?.fontWeight, FontWeight.w400);
|
||||
expect(text.style?.fontSize, 11);
|
||||
expect(text.style?.color, AppColors.textPrimary);
|
||||
final badge = tester.widget<Container>(
|
||||
find.ancestor(of: label, matching: find.byType(Container)).first,
|
||||
);
|
||||
final decoration = badge.decoration! as BoxDecoration;
|
||||
expect(decoration.color, AppColors.brandRed);
|
||||
expect(decoration.borderRadius, BorderRadius.circular(6));
|
||||
expect(decoration.boxShadow, isNull);
|
||||
expect(
|
||||
badge.padding,
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
);
|
||||
final position = tester.widget<Positioned>(
|
||||
find.ancestor(of: label, matching: find.byType(Positioned)).first,
|
||||
);
|
||||
expect(position.left, 8);
|
||||
expect(position.top, 8);
|
||||
await tester.tap(label);
|
||||
expect(taps, 1);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _host(String tag, VoidCallback onTap) => MaterialApp(
|
||||
locale: const Locale('zh'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: SizedBox(
|
||||
width: _posterWidth,
|
||||
height: _posterHeight,
|
||||
child: HomePosterCard(
|
||||
drama: Drama(
|
||||
id: 'poster',
|
||||
title: '测试短剧',
|
||||
synopsis: '',
|
||||
posterUrl: '',
|
||||
tag: tag,
|
||||
),
|
||||
badge: _fallback,
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,105 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:验证首页跨页动画的目标保留、连点打断和减少动画行为。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/pages/recommend/home_tab_motion_controller.dart';
|
||||
|
||||
const _midpoint = Duration(milliseconds: 90);
|
||||
const _finish = Duration(milliseconds: 300);
|
||||
|
||||
Widget _pager(HomeTabMotionController controller, List<int> accepted) =>
|
||||
MaterialApp(
|
||||
home: PageView(
|
||||
controller: controller,
|
||||
onPageChanged: (index) {
|
||||
if (controller.acceptsSelection(index)) accepted.add(index);
|
||||
},
|
||||
children: const [
|
||||
ColoredBox(color: Colors.black),
|
||||
ColoredBox(color: Colors.red),
|
||||
ColoredBox(color: Colors.grey),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
void main() {
|
||||
testWidgets(
|
||||
'should_ignore_intermediate_page_when_clicking_across_two_pages',
|
||||
(tester) async {
|
||||
int? settled;
|
||||
final controller = HomeTabMotionController(
|
||||
onSettled: (index) => settled = index,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
final accepted = <int>[];
|
||||
await tester.pumpWidget(_pager(controller, accepted));
|
||||
final transition = controller.selectPage(2, reduceMotion: false);
|
||||
await tester.pump();
|
||||
await tester.pump(_midpoint);
|
||||
expect(controller.pagePosition, inExclusiveRange(0.0, 2.0));
|
||||
expect(controller.acceptsSelection(1), isFalse);
|
||||
expect(accepted, isNot(contains(1)));
|
||||
await tester.pump(_finish);
|
||||
await transition;
|
||||
expect(controller.pagePosition, 2);
|
||||
expect(settled, 2);
|
||||
expect(controller.acceptsSelection(1), isTrue);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('should_keep_last_target_when_clicks_interrupt_each_other', (
|
||||
tester,
|
||||
) async {
|
||||
int? settled;
|
||||
final controller = HomeTabMotionController(
|
||||
onSettled: (index) => settled = index,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
await tester.pumpWidget(_pager(controller, []));
|
||||
final first = controller.selectPage(2, reduceMotion: false);
|
||||
await tester.pump();
|
||||
await tester.pump(_midpoint);
|
||||
final last = controller.selectPage(0, reduceMotion: false);
|
||||
await tester.pump();
|
||||
expect(controller.acceptsSelection(2), isFalse);
|
||||
await tester.pump(_finish);
|
||||
await Future.wait([first, last]);
|
||||
expect(controller.pagePosition, 0);
|
||||
expect(settled, 0);
|
||||
});
|
||||
|
||||
testWidgets('should_jump_to_target_when_reduced_motion_is_enabled', (
|
||||
tester,
|
||||
) async {
|
||||
int? settled;
|
||||
final controller = HomeTabMotionController(
|
||||
onSettled: (index) => settled = index,
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
await tester.pumpWidget(_pager(controller, []));
|
||||
await controller.selectPage(2, reduceMotion: true);
|
||||
expect(controller.pagePosition, 2);
|
||||
expect(settled, 2);
|
||||
});
|
||||
|
||||
testWidgets('should_release_target_when_user_drag_interrupts_animation', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = HomeTabMotionController(onSettled: (_) {});
|
||||
addTearDown(controller.dispose);
|
||||
await tester.pumpWidget(_pager(controller, []));
|
||||
final transition = controller.selectPage(2, reduceMotion: false);
|
||||
await tester.pump();
|
||||
await tester.pump(_midpoint);
|
||||
final gesture = await tester.startGesture(
|
||||
tester.getCenter(find.byType(PageView)),
|
||||
);
|
||||
await gesture.moveBy(const Offset(60, 0));
|
||||
await tester.pump();
|
||||
await transition;
|
||||
expect(controller.acceptsSelection(0), isTrue);
|
||||
expect(controller.acceptsSelection(1), isTrue);
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
}
|
||||
@@ -44,11 +44,11 @@ void main() {
|
||||
final recommendText = tester.widget<Text>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('recommend-tab-recommend')),
|
||||
matching: find.text('Recommend'),
|
||||
matching: find.text('Featured'),
|
||||
),
|
||||
);
|
||||
expect(recommendText.style?.fontSize, 17);
|
||||
expect(recommendText.style?.fontWeight, FontWeight.w700);
|
||||
expect(recommendText.style?.fontSize, 18);
|
||||
expect(recommendText.style?.fontWeight, FontWeight.w400);
|
||||
expect(recommendText.style?.color, AppColors.textPrimary);
|
||||
final playlistText = tester.widget<Text>(
|
||||
find.descendant(
|
||||
@@ -56,10 +56,17 @@ void main() {
|
||||
matching: find.text('Playlists'),
|
||||
),
|
||||
);
|
||||
expect(playlistText.style?.fontSize, 17);
|
||||
expect(playlistText.style?.fontSize, 18);
|
||||
// 字形度量固定,未选中态靠绘制缩放和灰色区分,切换时不重新排版。
|
||||
expect(playlistText.style?.fontWeight, FontWeight.w400);
|
||||
// 新版顶部 tab 未选中态也用主文字色,选中态只靠字重和下划线区分。
|
||||
expect(playlistText.style?.color, AppColors.textPrimary);
|
||||
expect(playlistText.style?.color, AppColors.homeTabInactive);
|
||||
final playlistScale = tester.widget<Transform>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('recommend-tab-playlist')),
|
||||
matching: find.byType(Transform),
|
||||
),
|
||||
);
|
||||
expect(playlistScale.transform.entry(0, 0), closeTo(0.9, 0.0001));
|
||||
expect(
|
||||
tester
|
||||
.widget<IconButton>(find.byKey(const Key('recommend-search')))
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// Author: fruit.com.cn
|
||||
// 职责:验证首页三标签文字和下划线随页面进度连续变化。
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:fruitshorts/core/theme/app_colors.dart';
|
||||
import 'package:fruitshorts/l10n/app_localizations.dart';
|
||||
import 'package:fruitshorts/pages/recommend/recommend_types.dart';
|
||||
import 'package:fruitshorts/pages/recommend/widgets/recommend_top_nav.dart';
|
||||
|
||||
const _pagePosition = 0.35;
|
||||
const _duration = Duration(milliseconds: 90);
|
||||
const _epsilon = 0.0001;
|
||||
|
||||
Widget _host({
|
||||
double? position,
|
||||
RecommendInnerTab tab = RecommendInnerTab.feed,
|
||||
}) {
|
||||
return MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: RecommendTopNav(
|
||||
selected: tab,
|
||||
pagePosition: position,
|
||||
onSelected: (_) {},
|
||||
onSearch: () {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _scale(WidgetTester tester, String tab) => tester
|
||||
.widget<Transform>(
|
||||
find.descendant(
|
||||
of: find.byKey(Key('recommend-tab-$tab')),
|
||||
matching: find.byType(Transform),
|
||||
),
|
||||
)
|
||||
.transform
|
||||
.entry(0, 0);
|
||||
|
||||
Color _underline(WidgetTester tester, String tab) {
|
||||
final line = tester.widget<Container>(
|
||||
find.byKey(Key('recommend-tab-$tab-underline')),
|
||||
);
|
||||
return (line.decoration! as BoxDecoration).color!;
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets(
|
||||
'should_brighten_only_inactive_home_tabs_when_selection_changes',
|
||||
(tester) async {
|
||||
const inactive = Color(0xFFC7C7CC);
|
||||
const codes = ['recommend', 'playlist', 'all'];
|
||||
for (final tab in RecommendInnerTab.values) {
|
||||
await tester.pumpWidget(
|
||||
_host(position: tab.index.toDouble(), tab: tab),
|
||||
);
|
||||
for (final (index, code) in codes.indexed) {
|
||||
final text = tester.widget<Text>(
|
||||
find.descendant(
|
||||
of: find.byKey(Key('recommend-tab-$code')),
|
||||
matching: find.byType(Text),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
text.style?.color,
|
||||
index == tab.index ? AppColors.textPrimary : inactive,
|
||||
);
|
||||
}
|
||||
}
|
||||
await tester.pumpWidget(_host(position: _pagePosition));
|
||||
final text = tester.widget<Text>(
|
||||
find.descendant(
|
||||
of: find.byKey(const Key('recommend-tab-playlist')),
|
||||
matching: find.byType(Text),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
text.style?.color,
|
||||
Color.lerp(inactive, AppColors.textPrimary, _pagePosition),
|
||||
);
|
||||
expect(AppColors.textTabInactive, const Color(0xFFB0B0B0));
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('should_keep_regular_enlarged_labels_for_every_selection', (
|
||||
tester,
|
||||
) async {
|
||||
for (final tab in RecommendInnerTab.values) {
|
||||
await tester.pumpWidget(_host(position: tab.index.toDouble(), tab: tab));
|
||||
for (final code in ['recommend', 'playlist', 'all']) {
|
||||
final text = tester.widget<Text>(
|
||||
find.descendant(
|
||||
of: find.byKey(Key('recommend-tab-$code')),
|
||||
matching: find.byType(Text),
|
||||
),
|
||||
);
|
||||
expect(text.style?.fontSize, 18);
|
||||
expect(text.style?.fontWeight, FontWeight.w400);
|
||||
}
|
||||
expect(tester.takeException(), isNull);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('should_follow_page_position_without_delayed_label_animation', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_host(position: 0));
|
||||
final first = tester.getRect(
|
||||
find.byKey(const Key('recommend-tab-recommend')),
|
||||
);
|
||||
final second = tester.getRect(
|
||||
find.byKey(const Key('recommend-tab-playlist')),
|
||||
);
|
||||
await tester.pumpWidget(
|
||||
_host(position: _pagePosition, tab: RecommendInnerTab.playlists),
|
||||
);
|
||||
expect(_scale(tester, 'recommend'), closeTo(0.965, _epsilon));
|
||||
expect(_scale(tester, 'playlist'), closeTo(0.935, _epsilon));
|
||||
expect(_underline(tester, 'recommend').a, closeTo(0.65, _epsilon));
|
||||
expect(_underline(tester, 'playlist').a, closeTo(0.35, _epsilon));
|
||||
expect(_underline(tester, 'all').a, 0);
|
||||
expect(
|
||||
tester.getRect(find.byKey(const Key('recommend-tab-recommend'))),
|
||||
first,
|
||||
);
|
||||
expect(
|
||||
tester.getRect(find.byKey(const Key('recommend-tab-playlist'))),
|
||||
second,
|
||||
);
|
||||
await tester.pumpWidget(_host(position: 0));
|
||||
expect(_scale(tester, 'recommend'), 1);
|
||||
expect(_underline(tester, 'recommend'), AppColors.brandRed);
|
||||
});
|
||||
|
||||
testWidgets('should_fade_both_underlines_when_used_without_page_position', (
|
||||
tester,
|
||||
) async {
|
||||
await tester.pumpWidget(_host());
|
||||
await tester.pumpWidget(_host(tab: RecommendInnerTab.playlists));
|
||||
await tester.pump(_duration);
|
||||
expect(_underline(tester, 'recommend').a, inExclusiveRange(0.0, 1.0));
|
||||
expect(_underline(tester, 'playlist').a, inExclusiveRange(0.0, 1.0));
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user