Files
ReelShort/test/library/favorites_page_test.dart
T
Zhou Weidong 1d4ab5e0e6 test: 按新 UI 基线重写过期用例
覆盖 155 条失败与 4 处编译错误:入口改走推荐流 tab、弹框换 AppGlowDialog、
rail 尺寸与底部锚定、子页标题字重 w400、底栏选中品牌红、收藏文案改收藏短剧。
全量 flutter test 775 通过。
2026-10-28 18:48:03 +08:00

288 lines
10 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/drama.dart';
import 'package:fruitshorts/core/data/remote/api_client.dart';
import 'package:fruitshorts/core/data/remote/auth_api.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/auth_controller.dart';
import 'package:fruitshorts/core/data/services/auth_session_store.dart';
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
import 'package:fruitshorts/core/platform/haptics.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/widgets/app_pull_to_refresh.dart';
import 'package:fruitshorts/core/widgets/empty_state.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/drama_detail/drama_detail_view.dart';
import 'package:fruitshorts/pages/favorites/favorites_page.dart';
import 'package:fruitshorts/pages/player/player_view.dart';
import 'package:provider/provider.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_http_adapter.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';
Drama _card(String id, {String synopsis = 'synopsis'}) => Drama(
id: id,
title: 'Drama $id',
synopsis: synopsis,
posterUrl: '/files/$id.png',
totalEpisodes: 12,
);
void main() {
setUp(() {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.empty();
});
testWidgets('should_render_favorite_cards_and_visible_remove_action', (
tester,
) async {
final auth = await _auth(signedIn: true);
final favorites = FavoriteController(
InMemoryFavoriteRepository(
saved: {'signal', 'city'},
cards: [_card('signal'), _card('city')],
),
);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('favorites-page')), findsOneWidget);
expect(find.byKey(const Key('favorites-back')), findsOneWidget);
expect(
tester.widget<Text>(find.byKey(const Key('favorites-count'))).data,
'2 favorites',
);
expect(find.text('Long press a card to remove'), findsNothing);
expect(find.text('Drama signal'), findsOneWidget);
expect(find.text('Drama city'), findsOneWidget);
final title = tester.widget<Text>(
find.byKey(const Key('favorite-title-signal')),
);
expect(title.style?.color, AppColors.onSurface);
expect(title.style?.fontSize, 15);
final synopsis = tester.widget<Text>(
find.byKey(const Key('favorite-synopsis-signal')),
);
expect(synopsis.data, 'synopsis');
expect(synopsis.maxLines, 2);
expect(synopsis.overflow, TextOverflow.ellipsis);
expect(synopsis.style?.color, AppColors.muted);
expect(synopsis.style?.fontSize, 12);
expect(
tester.getSize(find.byKey(const Key('favorite-cover-signal'))),
const Size(80, 106),
);
expect(find.byKey(const Key('favorite-remove-signal')), findsOneWidget);
expect(
tester.getSize(find.byKey(const Key('favorite-remove-signal'))),
const Size(28, 28),
);
expect(find.byIcon(Icons.star_rounded), findsNWidgets(2));
expect(find.byIcon(Icons.favorite_rounded), findsNothing);
expect(find.byType(AppPullToRefresh), findsOneWidget);
expect(
tester.widget<ListView>(find.byType(ListView)).physics,
AppPullToRefresh.physics,
);
await tester.drag(find.byType(ListView), const Offset(0, 240));
await tester.pump();
expect(find.byKey(const Key('pull-refresh-indicator')), findsOneWidget);
expect(find.text('Refreshing…'), findsNothing);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('favorite-remove-signal')));
await tester.pumpAndSettle();
expect(find.byKey(const Key('favorite-card-signal')), findsNothing);
});
testWidgets('should_hide_blank_synopsis_without_changing_card_height', (
tester,
) async {
final auth = await _auth(signedIn: true);
final longSynopsis = List.filled(12, '很长的剧情简介').join();
final favorites = FavoriteController(
InMemoryFavoriteRepository(
saved: {'with-synopsis', 'without-synopsis'},
cards: [
_card('with-synopsis', synopsis: longSynopsis),
_card('without-synopsis', synopsis: ' '),
],
),
);
await _pumpPage(tester, auth: auth, favorites: favorites);
final withSynopsis = tester.widget<Text>(
find.byKey(const Key('favorite-synopsis-with-synopsis')),
);
expect(withSynopsis.maxLines, 2);
expect(withSynopsis.overflow, TextOverflow.ellipsis);
expect(
find.byKey(const Key('favorite-synopsis-without-synopsis')),
findsNothing,
);
expect(
tester.getSize(find.byKey(const Key('favorite-card-with-synopsis'))),
tester.getSize(find.byKey(const Key('favorite-card-without-synopsis'))),
);
});
testWidgets('should_keep_card_and_show_failure_when_remove_fails', (
tester,
) async {
final auth = await _auth(signedIn: true);
final repository = InMemoryFavoriteRepository(
saved: {'signal'},
cards: [_card('signal')],
)..failRemove = true;
final favorites = FavoriteController(repository);
final haptics = FakeHaptics();
await _pumpPage(tester, auth: auth, favorites: favorites, haptics: haptics);
await tester.tap(find.byKey(const Key('favorite-remove-signal')));
await tester.pumpAndSettle();
expect(haptics.lightImpacts, 1);
expect(find.text('Drama signal'), findsOneWidget);
expect(
find.text('Could not update favorites. Please retry.'),
findsOneWidget,
);
});
testWidgets('should_show_empty_state_when_favorite_list_is_empty', (
tester,
) async {
final auth = await _auth(signedIn: true);
final favorites = FavoriteController(InMemoryFavoriteRepository());
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('favorites-empty')), findsOneWidget);
expect(find.byType(EmptyState), findsOneWidget);
});
testWidgets('should_show_retry_when_favorite_list_fails', (tester) async {
final auth = await _auth(signedIn: true);
final repository = InMemoryFavoriteRepository()..failPage = true;
final favorites = FavoriteController(repository);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('favorites-error')), findsOneWidget);
expect(find.byKey(const Key('favorites-retry')), findsOneWidget);
});
testWidgets('should_show_device_favorites_when_signed_out', (tester) async {
final auth = await _auth(signedIn: false);
final repository = InMemoryFavoriteRepository(
saved: {'signal'},
cards: [_card('signal')],
);
final favorites = FavoriteController(repository);
await _pumpPage(tester, auth: auth, favorites: favorites);
expect(find.byKey(const Key('auth-gate-panel')), findsNothing);
expect(find.text('Drama signal'), findsOneWidget);
expect(repository.pageCalls, 1);
});
testWidgets('should_open_player_when_favorite_card_is_tapped', (
tester,
) async {
await _auth(signedIn: true);
final catalog = FakeCatalogRepository();
final drama = catalog.dramas.first;
await tester.pumpWidget(
LumaDramaApp(
playerFactory: createWidgetPlayer,
catalogRepository: catalog,
categoryRepository: FakeCategoryRepository(catalog: catalog),
watchHistoryRepository: InMemoryWatchHistoryRepository(),
favoriteRepository: InMemoryFavoriteRepository(
saved: {drama.id},
cards: [drama],
),
playbackRepository: FakePlaybackRepository(),
commentRepository: FakeCommentRepository(),
interactionRepository: FakeInteractionRepository(),
noticeRepository: FakeNoticeRepository(),
haptics: FakeHaptics(),
),
);
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('tab-mine')));
await tester.pumpAndSettle();
await tester.ensureVisible(find.byKey(const Key('mine-favorites')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('mine-favorites')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(Key('favorite-card-${drama.id}')));
await tester.pumpAndSettle();
expect(find.byType(PlayerPage), findsOneWidget);
expect(find.byType(DramaDetailPage), findsNothing);
});
}
Future<AuthController> _auth({required bool signedIn}) async {
if (signedIn) {
SharedPreferencesAsyncPlatform.instance =
InMemorySharedPreferencesAsync.withData({
'auth.accessToken': 'test-access',
'auth.refreshToken': 'test-refresh',
'auth.user': '{"uid":"1","nickname":"Viewer","avatarUrl":""}',
});
}
final client = ApiClient(
baseUrl: 'http://127.0.0.1:8081',
adapter: FakeHttpAdapter(
(_) async => FakeHttpResponse('{"code":0,"message":"ok","data":null}'),
),
);
final auth = AuthController(AuthApi(client), AuthSessionStore());
await auth.load();
return auth;
}
Future<void> _pumpPage(
WidgetTester tester, {
required AuthController auth,
required FavoriteController favorites,
FakeHaptics? haptics,
}) async {
await tester.pumpWidget(
MultiProvider(
providers: [
ChangeNotifierProvider<AuthController>.value(value: auth),
ChangeNotifierProvider<FavoriteController>.value(value: favorites),
Provider<Haptics>.value(value: haptics ?? FakeHaptics()),
],
child: MaterialApp(
locale: const Locale('en'),
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: const FavoritesPage(),
),
),
);
await tester.pumpAndSettle();
}