fix(favorite): 统一收藏状态与互动图标

This commit is contained in:
Zhou Weidong committed 2026-10-27 09:31:48 +08:00
1 parent 4b97f89c0b
commit d3dbc9f8dc
13 files changed
+347 -213

No files matched your search

+40 -23
View File
@@ -16,20 +16,20 @@ DramaDetailLogic _logic(
FakeWatchHistory history, {
FavoriteController? favorites,
PlaybackPositionCache? positionCache,
}) =>
DramaDetailLogic(
drama: drama,
history: history,
playback: FakePlaybackRepository(),
favorites: favorites ?? FavoriteController(InMemoryFavoriteRepository()),
positionCache: positionCache ?? PlaybackPositionCache(),
);
}) => DramaDetailLogic(
drama: drama,
history: history,
playback: FakePlaybackRepository(),
favorites: favorites ?? FavoriteController(InMemoryFavoriteRepository()),
positionCache: positionCache ?? PlaybackPositionCache(),
);
Drama _card(String id) => Drama(
Drama _card(String id, {int? favoriteCount}) => Drama(
id: id,
title: id,
synopsis: 'synopsis',
posterUrl: '/files/$id.jpg',
favoriteCount: favoriteCount,
totalEpisodes: 3,
episodes: [
Episode(
@@ -42,11 +42,12 @@ Drama _card(String id) => Drama(
],
);
Drama _detail(String id) => Drama(
Drama _detail(String id, {int? favoriteCount}) => Drama(
id: id,
title: id,
synopsis: 'synopsis',
posterUrl: '/files/$id.jpg',
favoriteCount: favoriteCount,
totalEpisodes: 3,
detailLoaded: true,
episodes: [
@@ -62,21 +63,24 @@ Drama _detail(String id) => Drama(
);
void main() {
test('should_upgrade_to_full_episode_list_when_detail_loads_for_list_card', () async {
var calls = 0;
final logic = _logic(_card('signal'), FakeWatchHistory());
test(
'should_upgrade_to_full_episode_list_when_detail_loads_for_list_card',
() async {
var calls = 0;
final logic = _logic(_card('signal'), FakeWatchHistory());
await logic.load(() async {
calls++;
return _detail('signal');
});
await logic.load(() async {
calls++;
return _detail('signal');
});
expect(calls, 1);
expect(logic.loading, isFalse);
expect(logic.failed, isFalse);
expect(logic.drama.episodes, hasLength(3));
logic.dispose();
});
expect(calls, 1);
expect(logic.loading, isFalse);
expect(logic.failed, isFalse);
expect(logic.drama.episodes, hasLength(3));
logic.dispose();
},
);
test('should_keep_card_and_report_reason_when_load_fails', () async {
final logic = _logic(_card('signal'), FakeWatchHistory());
@@ -107,6 +111,19 @@ void main() {
logic.dispose();
});
test('should_use_detail_favorite_count_as_authoritative', () async {
final favorites = FavoriteController(InMemoryFavoriteRepository());
final card = _card('signal', favoriteCount: 2);
expect(favorites.favoriteCountFor(card), 2);
final logic = _logic(card, FakeWatchHistory(), favorites: favorites);
await logic.load(() async => _detail('signal', favoriteCount: 9));
expect(favorites.favoriteCount('signal'), 9);
expect(logic.adjustedFavoriteCount, 9);
logic.dispose();
});
test('should_prefer_recent_local_position_when_loading_continue', () async {
final cache = PlaybackPositionCache();
cache.save(
+50 -10
View File
@@ -323,19 +323,59 @@ void main() {
tester.widget<Text>(find.byKey(const Key('rail-save-count'))).data,
'3.2万',
);
expect(
final likeImage = tester.widget<Image>(
find.descendant(
of: find.byKey(const Key('rail-like')),
matching: find.byIcon(Icons.favorite_rounded),
matching: find.byType(Image),
),
findsOneWidget,
);
expect(
final saveImage = tester.widget<Image>(
find.descendant(
of: find.byKey(const Key('rail-save')),
matching: find.byIcon(Icons.bookmark_border_rounded),
matching: find.byType(Image),
),
findsOneWidget,
);
expect(
(likeImage.image as AssetImage).assetName,
'assets/icon/rail/rail_like.png',
);
expect(likeImage.color, AppColors.doubleTapHeartRed);
expect(
(saveImage.image as AssetImage).assetName,
'assets/icon/rail/rail_save.png',
);
expect(saveImage.color, AppColors.onPrimary);
expect(find.byKey(const Key('rail-comments')), findsOneWidget);
expect(find.byKey(const Key('rail-share')), findsOneWidget);
expect(
tester.widget<Text>(find.byKey(const Key('rail-comments-count'))).data,
'12',
);
expect(
(tester
.widget<Image>(
find.descendant(
of: find.byKey(const Key('rail-comments')),
matching: find.byType(Image),
),
)
.image
as AssetImage)
.assetName,
'assets/icon/rail/rail_comment.png',
);
expect(
(tester
.widget<Image>(
find.descendant(
of: find.byKey(const Key('rail-share')),
matching: find.byType(Image),
),
)
.image
as AssetImage)
.assetName,
'assets/icon/rail/rail_share.png',
);
expect(find.byKey(const Key('rail-episodes')), findsOneWidget);
expect(
@@ -345,9 +385,6 @@ void main() {
),
findsOneWidget,
);
expect(find.byKey(const Key('rail-comments')), findsNothing);
expect(find.byKey(const Key('rail-share')), findsNothing);
expect(find.text('Comments'), findsNothing);
expect(
tester.getRect(find.byKey(const Key('home-feed-action-rail'))).right,
closeTo(
@@ -672,7 +709,10 @@ void main() {
final previewDecoration = previewContainer.decoration! as BoxDecoration;
expect(previewDecoration.color, AppColors.card);
expect(previewDecoration.borderRadius, BorderRadius.circular(8));
expect((previewDecoration.border! as Border).top.color, AppColors.onPrimary);
expect(
(previewDecoration.border! as Border).top.color,
AppColors.onPrimary,
);
await gesture.moveBy(const Offset(-600, 0));
await tester.pump();
expect(
+27 -1
View File
@@ -6,11 +6,12 @@ import 'package:fruitshorts/core/data/models/page_result.dart';
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
Drama _card(String id) => Drama(
Drama _card(String id, {int? favoriteCount}) => Drama(
id: id,
title: 'Drama $id',
synopsis: 'synopsis',
posterUrl: '/files/$id.png',
favoriteCount: favoriteCount,
);
void main() {
@@ -37,6 +38,31 @@ void main() {
expect(controller.cards.map((drama) => drama.id), ['signal']);
});
test(
'should_adjust_the_single_favorite_count_when_toggle_succeeds',
() async {
final controller = FavoriteController(InMemoryFavoriteRepository());
final card = _card('signal', favoriteCount: 12);
expect(controller.favoriteCountFor(card), 12);
expect(await controller.toggle('signal', card: card), isTrue);
expect(controller.favoriteCount('signal'), 13);
expect(await controller.toggle('signal', card: card), isTrue);
expect(controller.favoriteCount('signal'), 12);
},
);
test('should_apply_server_count_and_roll_back_toggle_failure', () async {
final repository = InMemoryFavoriteRepository()..failToggle = true;
final controller = FavoriteController(repository);
controller.applyServerState('signal', saved: false, favoriteCount: 20);
expect(controller.favoriteCount('signal'), 20);
expect(await controller.toggle('signal'), isFalse);
expect(controller.isSaved('signal'), isFalse);
expect(controller.favoriteCount('signal'), 20);
});
test('should_remove_card_and_state_when_toggled_off', () async {
final repository = InMemoryFavoriteRepository(saved: {'signal'});
final controller = FavoriteController(repository);
+4 -3
View File
@@ -3,6 +3,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/services/interaction_controller.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/core/theme/app_theme.dart';
import 'package:fruitshorts/l10n/app_localizations.dart';
import 'package:fruitshorts/pages/player/widgets/home_feed_overlay.dart';
@@ -41,13 +42,13 @@ void main() {
);
expect(repository.likeTargets, hasLength(1));
expect(haptics.lightImpacts, 1);
expect(
final likeImage = tester.widget<Image>(
find.descendant(
of: find.byKey(const Key('rail-like')),
matching: find.byIcon(Icons.favorite_rounded),
matching: find.byType(Image),
),
findsOneWidget,
);
expect(likeImage.color, AppColors.doubleTapHeartRed);
}
});
+48 -4
View File
@@ -630,6 +630,53 @@ void main() {
expect(interactions.likeTargets, ['EPISODE:the-signal-demo-1']);
});
testWidgets('should_keep_favorite_count_when_player_saves_and_returns', (
tester,
) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
final initialCount = _railLabel(tester, const Key('rail-save'));
await tester.tap(find.byKey(const Key('home-feed-series-name')));
await tester.pumpAndSettle();
expect(_railLabel(tester, const Key('player-rail-save')), initialCount);
await tester.tap(find.byKey(const Key('player-rail-save')));
await tester.pumpAndSettle();
final savedCount = _railLabel(tester, const Key('player-rail-save'));
expect(savedCount, isNot(initialCount));
await tester.tap(find.byKey(const Key('player-back')));
await tester.pumpAndSettle();
expect(find.byType(AuthPage), findsNothing);
expect(_railLabel(tester, const Key('rail-save')), savedCount);
});
testWidgets('should_keep_favorite_state_when_recommend_opens_player', (
tester,
) async {
await _pumpApp(tester);
await tester.tap(find.byKey(const Key('tab-recommend')));
await tester.pumpAndSettle();
await tester.tap(find.byKey(const Key('rail-save')));
await tester.pumpAndSettle();
final savedCount = _railLabel(tester, const Key('rail-save'));
await tester.tap(find.byKey(const Key('home-feed-series-name')));
await tester.pumpAndSettle();
expect(_railLabel(tester, const Key('player-rail-save')), savedCount);
final saveImage = tester.widget<Image>(
find.descendant(
of: find.byKey(const Key('player-rail-save')),
matching: find.byType(Image),
),
);
expect(saveImage.color, AppColors.blue);
});
testWidgets('should_keep_video_surface_background_black', (tester) async {
await _pumpApp(tester);
await _openSeriesPlayer(tester);
@@ -646,9 +693,7 @@ void main() {
expect(find.byKey(const Key('player-fallback-scrim')), findsNothing);
});
testWidgets('should_open_comments_without_login', (
tester,
) async {
testWidgets('should_open_comments_without_login', (tester) async {
await _pumpApp(tester);
await _openSeriesPlayer(tester);
@@ -1914,7 +1959,6 @@ void main() {
find.text('Available unlock methods vary by title.'),
findsOneWidget,
);
});
testWidgets('should_require_login_before_opening_unlock_flow', (