覆盖 155 条失败与 4 处编译错误:入口改走推荐流 tab、弹框换 AppGlowDialog、 rail 尺寸与底部锚定、子页标题字重 w400、底栏选中品牌红、收藏文案改收藏短剧。 全量 flutter test 775 通过。
378 lines
14 KiB
Dart
378 lines
14 KiB
Dart
// 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/wallet.dart';
|
|
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
|
import 'package:fruitshorts/core/data/repositories/commerce_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/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/auth/auth_view.dart';
|
|
import 'package:fruitshorts/pages/mine/about_page.dart';
|
|
import 'package:fruitshorts/pages/mine/mine_types.dart';
|
|
import 'package:fruitshorts/pages/mine/widgets/mine_menu.dart';
|
|
import 'package:fruitshorts/pages/store/vip_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';
|
|
|
|
import '../support/fake_catalog.dart';
|
|
import '../support/fake_category.dart';
|
|
import '../support/fake_interaction_repositories.dart';
|
|
import '../support/fake_notice_repository.dart';
|
|
import '../support/widget_player.dart';
|
|
|
|
void main() {
|
|
testWidgets('should_render_visitor_header_vip_quick_actions_and_services', (
|
|
tester,
|
|
) async {
|
|
await _pumpApp(tester, signedIn: false);
|
|
await _openMine(tester);
|
|
|
|
expect(find.byKey(const Key('mine-user-row')), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-avatar')), findsOneWidget);
|
|
expect(find.text('Visitor'), findsOneWidget);
|
|
expect(find.text('UID: --'), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-uid-copy')), findsOneWidget);
|
|
final uidRowSize = tester.getSize(find.byKey(const Key('mine-uid')));
|
|
expect(uidRowSize.height, closeTo(AppDimens.mineUidRowHeight, 0.1));
|
|
final nickname = tester.widget<Text>(
|
|
find.byKey(const Key('mine-nickname')),
|
|
);
|
|
expect(nickname.style?.fontSize, AppDimens.mineUserName);
|
|
expect(nickname.style?.fontWeight, FontWeight.w400);
|
|
expect(nickname.style?.height, AppDimens.mineUserNameLineHeight);
|
|
final uid = tester.widget<Text>(find.text('UID: --'));
|
|
expect(uid.style?.fontSize, AppDimens.mineUid);
|
|
expect(uid.style?.color, AppColors.textTertiary);
|
|
final copyIcon = tester.widget<Icon>(
|
|
find.descendant(
|
|
of: find.byKey(const Key('mine-uid-copy')),
|
|
matching: find.byType(Icon),
|
|
),
|
|
);
|
|
expect(copyIcon.size, AppDimens.mineCopyIcon);
|
|
expect(copyIcon.color, AppColors.textTertiary);
|
|
expect(find.byKey(const Key('mine-login')), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-edit')), findsNothing);
|
|
expect(find.byKey(const Key('mine-vip')), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-vip-open')), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-mini-player')), findsNothing);
|
|
|
|
final surface = tester.widget<DecoratedBox>(
|
|
find.byKey(const Key('mine-page-surface')),
|
|
);
|
|
final surfaceDecoration = surface.decoration as BoxDecoration;
|
|
final background = surfaceDecoration.gradient! as LinearGradient;
|
|
expect(background.begin, Alignment.topCenter);
|
|
expect(background.end, Alignment.bottomCenter);
|
|
expect(background.colors, const [
|
|
AppColors.followingGlow,
|
|
AppColors.followingGlowMid,
|
|
AppColors.appBackground,
|
|
]);
|
|
expect(background.stops, const [0, 0.14, 0.3]);
|
|
|
|
// 快捷图标行:钱包 / 收藏 / 离线缓存 / 观看历史。
|
|
const quickActions = [
|
|
(Key('mine-wallet'), Icons.wallet_outlined),
|
|
(Key('mine-favorites'), Icons.star_border_rounded),
|
|
(Key('mine-downloads'), Icons.download_outlined),
|
|
(Key('mine-history'), Icons.history_rounded),
|
|
];
|
|
for (final action in quickActions) {
|
|
final icon = tester.widget<Icon>(
|
|
find.descendant(
|
|
of: find.byKey(action.$1),
|
|
matching: find.byIcon(action.$2),
|
|
),
|
|
);
|
|
expect(icon.color, AppColors.textPrimary);
|
|
expect(icon.size, AppDimens.mineQuickActionIcon);
|
|
}
|
|
|
|
// 「更多服务」列表:联系客服 / 语言 / 设置 / 通知。
|
|
const services = [
|
|
(Key('mine-help'), Icons.headset_outlined),
|
|
(Key('mine-language'), Icons.language_rounded),
|
|
(Key('mine-settings'), Icons.settings_outlined),
|
|
(Key('mine-notices'), Icons.notifications_none_rounded),
|
|
];
|
|
for (final service in services) {
|
|
expect(find.byKey(service.$1), findsOneWidget);
|
|
final icon = tester.widget<Icon>(
|
|
find.descendant(
|
|
of: find.byKey(service.$1),
|
|
matching: find.byIcon(service.$2),
|
|
),
|
|
);
|
|
expect(icon.color, AppColors.textPrimary);
|
|
expect(icon.size, AppDimens.mineServiceIcon);
|
|
}
|
|
expect(find.byKey(const Key('mine-menu')), findsOneWidget);
|
|
expect(tester.getTopLeft(find.byKey(const Key('mine-menu'))).dx, 14);
|
|
|
|
final vip = tester.widget<Container>(find.byKey(const Key('mine-vip')));
|
|
final vipDecoration = vip.decoration! as BoxDecoration;
|
|
expect((vipDecoration.gradient! as LinearGradient).colors, const [
|
|
AppColors.vipCardStart,
|
|
AppColors.vipCardEnd,
|
|
]);
|
|
expect(vipDecoration.boxShadow, isNull);
|
|
|
|
final order = [
|
|
tester.getTopLeft(find.byKey(const Key('mine-user-row'))).dy,
|
|
tester.getTopLeft(find.byKey(const Key('mine-vip'))).dy,
|
|
tester.getTopLeft(find.byKey(const Key('mine-wallet'))).dy,
|
|
tester.getTopLeft(find.byKey(const Key('mine-help'))).dy,
|
|
];
|
|
expect(order, [...order]..sort());
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.tap(find.byKey(const Key('mine-login')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(AuthPage), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_render_signed_in_user_without_floating_mini_player', (
|
|
tester,
|
|
) async {
|
|
await _pumpApp(
|
|
tester,
|
|
signedIn: true,
|
|
history: InMemoryWatchHistoryRepository(seed: [_recent]),
|
|
);
|
|
await _openMine(tester);
|
|
|
|
expect(find.text('Viewer'), findsOneWidget);
|
|
expect(find.text('UID: U-100'), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-edit')), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-login')), findsNothing);
|
|
expect(find.byKey(const Key('mine-mini-player')), findsNothing);
|
|
expect(find.byType(FloatingActionButton), findsNothing);
|
|
|
|
expect(find.byKey(const Key('mine-notices')), findsOneWidget);
|
|
await tester.tap(find.byKey(const Key('mine-edit')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('account-profile-page')), findsOneWidget);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('should_open_help_and_about_from_mine', (tester) async {
|
|
await _pumpApp(tester, signedIn: false);
|
|
await _openMine(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('mine-help')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('mine-placeholder-page')), findsOneWidget);
|
|
await tester.binding.handlePopRoute();
|
|
await tester.pumpAndSettle();
|
|
|
|
// 关于我们已收进「设置」二级页。
|
|
await tester.tap(find.byKey(const Key('mine-settings')));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(const Key('settings-about')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(MineAboutPage), findsOneWidget);
|
|
expect(find.byKey(const Key('mine-about-page')), findsOneWidget);
|
|
expect(find.text('Version 1.0.0'), findsOneWidget);
|
|
expect(
|
|
find.text(
|
|
AppLocalizations.of(
|
|
tester.element(find.byType(MineAboutPage)),
|
|
)!.aboutDescription,
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
});
|
|
|
|
testWidgets('should_open_watch_records_from_quick_actions', (tester) async {
|
|
await _pumpApp(
|
|
tester,
|
|
signedIn: true,
|
|
history: InMemoryWatchHistoryRepository(seed: [_recent]),
|
|
);
|
|
await _openMine(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('mine-history')));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const Key('watch-records-page')), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('should_open_vip_from_mine_without_floating_player', (
|
|
tester,
|
|
) async {
|
|
await _pumpApp(
|
|
tester,
|
|
signedIn: true,
|
|
history: InMemoryWatchHistoryRepository(seed: [_recent]),
|
|
);
|
|
await _openMine(tester);
|
|
|
|
await tester.tap(find.byKey(const Key('mine-vip-open')));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(VipPage), findsOneWidget);
|
|
await tester.binding.handlePopRoute();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(const Key('mine-mini-player')), findsNothing);
|
|
expect(find.byType(FloatingActionButton), findsNothing);
|
|
});
|
|
|
|
testWidgets('should_expose_quick_actions_and_service_destinations', (
|
|
tester,
|
|
) async {
|
|
tester.view.physicalSize = const Size(390, 900);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
final opened = <MineDestination>[];
|
|
var languageOpened = 0;
|
|
var favoritesOpened = 0;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: AppTheme.dark(),
|
|
locale: const Locale('en'),
|
|
supportedLocales: AppLocalizations.supportedLocales,
|
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
|
home: Scaffold(
|
|
body: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(AppDimens.pageHorizontal),
|
|
child: Column(
|
|
children: [
|
|
MineQuickActions(
|
|
onWallet: () => opened.add(MineDestination.wallet),
|
|
onFavorites: () => favoritesOpened++,
|
|
onDownloads: () => opened.add(MineDestination.downloads),
|
|
onHistory: () => opened.add(MineDestination.history),
|
|
),
|
|
MineServiceList(
|
|
onOpen: opened.add,
|
|
onLanguage: () => languageOpened++,
|
|
languageName: 'English',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
const quickActions = [
|
|
(Key('mine-wallet'), MineDestination.wallet),
|
|
(Key('mine-downloads'), MineDestination.downloads),
|
|
(Key('mine-history'), MineDestination.history),
|
|
];
|
|
for (final action in quickActions) {
|
|
await tester.tap(find.byKey(action.$1));
|
|
await tester.pump();
|
|
expect(opened.last, action.$2);
|
|
}
|
|
await tester.tap(find.byKey(const Key('mine-favorites')));
|
|
await tester.pump();
|
|
expect(favoritesOpened, 1);
|
|
|
|
const services = [
|
|
(Key('mine-help'), MineDestination.help),
|
|
(Key('mine-notices'), MineDestination.notices),
|
|
(Key('mine-settings'), MineDestination.settings),
|
|
];
|
|
for (final service in services) {
|
|
await tester.tap(find.byKey(service.$1));
|
|
await tester.pump();
|
|
expect(opened.last, service.$2);
|
|
}
|
|
await tester.tap(find.byKey(const Key('mine-language')));
|
|
await tester.pump();
|
|
expect(languageOpened, 1);
|
|
});
|
|
}
|
|
|
|
const _recent = WatchProgress(
|
|
dramaId: 'city-lights-demo',
|
|
episodeId: 'city-lights-demo-3',
|
|
dramaTitle: 'Recent drama',
|
|
dramaCover: '/files/recent.jpg',
|
|
episodeNumber: 3,
|
|
totalEpisodes: 8,
|
|
position: Duration(seconds: 30),
|
|
duration: Duration(minutes: 2),
|
|
);
|
|
|
|
Future<void> _openMine(WidgetTester tester) async {
|
|
await tester.tap(find.byKey(const Key('tab-mine')));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
Future<void> _pumpApp(
|
|
WidgetTester tester, {
|
|
required bool signedIn,
|
|
WatchHistoryRepository? history,
|
|
FavoriteRepository? favorite,
|
|
CommerceRepository? commerce,
|
|
}) async {
|
|
tester.view.physicalSize = const Size(390, 1000);
|
|
tester.view.devicePixelRatio = 1;
|
|
addTearDown(tester.view.resetPhysicalSize);
|
|
addTearDown(tester.view.resetDevicePixelRatio);
|
|
SharedPreferencesAsyncPlatform.instance = signedIn
|
|
? InMemorySharedPreferencesAsync.withData({
|
|
'auth.accessToken': 'access',
|
|
'auth.refreshToken': 'refresh',
|
|
'auth.user': '{"uid":"U-100","nickname":"Viewer","avatarUrl":""}',
|
|
})
|
|
: InMemorySharedPreferencesAsync.empty();
|
|
final catalog = FakeCatalogRepository();
|
|
await tester.pumpWidget(
|
|
LumaDramaApp(
|
|
playerFactory: createWidgetPlayer,
|
|
catalogRepository: catalog,
|
|
categoryRepository: FakeCategoryRepository(catalog: catalog),
|
|
watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(),
|
|
favoriteRepository:
|
|
favorite ??
|
|
InMemoryFavoriteRepository(
|
|
saved: catalog.dramas.map((drama) => drama.id).toSet(),
|
|
cards: catalog.dramas,
|
|
),
|
|
playbackRepository: FakePlaybackRepository(),
|
|
commentRepository: FakeCommentRepository(),
|
|
interactionRepository: FakeInteractionRepository(),
|
|
noticeRepository: FakeNoticeRepository(),
|
|
commerceRepository: commerce ?? const _MineWalletCommerceRepository(),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
class _MineWalletCommerceRepository implements CommerceRepository {
|
|
const _MineWalletCommerceRepository();
|
|
|
|
@override
|
|
Future<WalletSnapshot> fetchWallet() async {
|
|
return const WalletSnapshot(
|
|
wallet: CoinWallet(
|
|
coinBalance: 320,
|
|
totalRechargedCoin: 500,
|
|
totalConsumedCoin: 180,
|
|
totalRefundedCoin: 0,
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Future<UnlockResult> unlock(String episodeId) async {
|
|
return const UnlockResult(
|
|
unlocked: true,
|
|
accessType: 'UNLOCKED',
|
|
coinCost: 0,
|
|
coinBalance: 320,
|
|
);
|
|
}
|
|
}
|