186 lines
5.4 KiB
Dart
186 lines
5.4 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:fruitshorts/core/data/models/drama.dart';
|
|
import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
|
import 'package:fruitshorts/core/data/remote/api_client.dart';
|
|
import 'package:fruitshorts/core/data/repositories/favorite_repository.dart';
|
|
import 'package:fruitshorts/core/data/repositories/playback_repository.dart';
|
|
import 'package:fruitshorts/core/data/services/favorite_controller.dart';
|
|
import 'package:fruitshorts/core/data/services/playback_position_cache.dart';
|
|
import 'package:fruitshorts/pages/drama_detail/drama_detail_logic.dart';
|
|
|
|
import '../support/fake_watch_history.dart';
|
|
|
|
/// 详情页逻辑依赖:票据仓库 + 收藏控制器(测试里都是内存实现)。
|
|
DramaDetailLogic _logic(
|
|
Drama drama,
|
|
FakeWatchHistory history, {
|
|
FavoriteController? favorites,
|
|
PlaybackPositionCache? positionCache,
|
|
}) =>
|
|
DramaDetailLogic(
|
|
drama: drama,
|
|
history: history,
|
|
playback: FakePlaybackRepository(),
|
|
favorites: favorites ?? FavoriteController(InMemoryFavoriteRepository()),
|
|
positionCache: positionCache ?? PlaybackPositionCache(),
|
|
);
|
|
|
|
Drama _card(String id) => Drama(
|
|
id: id,
|
|
title: id,
|
|
synopsis: 'synopsis',
|
|
posterUrl: '/files/$id.jpg',
|
|
totalEpisodes: 3,
|
|
episodes: [
|
|
Episode(
|
|
id: '$id-1',
|
|
number: 1,
|
|
title: '第 1 集',
|
|
isFree: true,
|
|
videoUrl: '/files/$id-1.mp4',
|
|
),
|
|
],
|
|
);
|
|
|
|
Drama _detail(String id) => Drama(
|
|
id: id,
|
|
title: id,
|
|
synopsis: 'synopsis',
|
|
posterUrl: '/files/$id.jpg',
|
|
totalEpisodes: 3,
|
|
detailLoaded: true,
|
|
episodes: [
|
|
for (var number = 1; number <= 3; number++)
|
|
Episode(
|
|
id: '$id-$number',
|
|
number: number,
|
|
title: '第 $number 集',
|
|
isFree: true,
|
|
videoUrl: '/files/$id-$number.mp4',
|
|
),
|
|
],
|
|
);
|
|
|
|
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());
|
|
|
|
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();
|
|
});
|
|
|
|
test('should_keep_card_and_report_reason_when_load_fails', () async {
|
|
final logic = _logic(_card('signal'), FakeWatchHistory());
|
|
|
|
await logic.load(() async => throw const ApiException(500, '服务异常'));
|
|
|
|
expect(logic.failed, isTrue);
|
|
expect(logic.failureMessage, '服务异常');
|
|
expect(logic.drama.episodes, hasLength(1));
|
|
|
|
await logic.load(() async => throw StateError('offline'));
|
|
expect(logic.failed, isTrue);
|
|
expect(logic.failureMessage, isNull);
|
|
logic.dispose();
|
|
});
|
|
|
|
test('should_skip_request_when_detail_is_already_loaded', () async {
|
|
var calls = 0;
|
|
final logic = _logic(_detail('signal'), FakeWatchHistory());
|
|
|
|
await logic.load(() async {
|
|
calls++;
|
|
return _detail('signal');
|
|
});
|
|
|
|
expect(calls, 0);
|
|
expect(logic.drama.episodes, hasLength(3));
|
|
logic.dispose();
|
|
});
|
|
|
|
test('should_prefer_recent_local_position_when_loading_continue', () async {
|
|
final cache = PlaybackPositionCache();
|
|
cache.save(
|
|
episodeId: 'signal-3',
|
|
dramaId: 'signal',
|
|
position: const Duration(minutes: 1, seconds: 20),
|
|
duration: const Duration(minutes: 3),
|
|
);
|
|
final history = FakeWatchHistory()
|
|
..records['signal'] = const WatchProgress(
|
|
dramaId: 'signal',
|
|
episodeId: 'signal-1',
|
|
position: Duration(seconds: 5),
|
|
duration: Duration(minutes: 3),
|
|
);
|
|
final logic = _logic(_detail('signal'), history, positionCache: cache);
|
|
|
|
await logic.load(() async => _detail('signal'));
|
|
|
|
expect(logic.continueIndex, 2);
|
|
expect(logic.progress?.position, const Duration(seconds: 80));
|
|
expect(history.reads, isEmpty);
|
|
logic.dispose();
|
|
});
|
|
|
|
test('should_adjust_favorite_count_only_after_successful_changes', () async {
|
|
final repository = InMemoryFavoriteRepository(cards: [_favoriteDrama]);
|
|
final favorites = FavoriteController(repository);
|
|
await favorites.load();
|
|
final logic = _logic(
|
|
_favoriteDrama,
|
|
FakeWatchHistory(),
|
|
favorites: favorites,
|
|
);
|
|
|
|
expect(logic.adjustedFavoriteCount, 2);
|
|
expect(await logic.toggleFavorite(), isTrue);
|
|
expect(logic.adjustedFavoriteCount, 3);
|
|
|
|
repository.failToggle = true;
|
|
expect(await logic.toggleFavorite(), isFalse);
|
|
expect(logic.adjustedFavoriteCount, 3);
|
|
|
|
repository.failToggle = false;
|
|
expect(await logic.toggleFavorite(), isTrue);
|
|
expect(logic.adjustedFavoriteCount, 2);
|
|
logic.dispose();
|
|
});
|
|
|
|
test('should_decrement_favorite_count_from_saved_state', () async {
|
|
final repository = InMemoryFavoriteRepository(
|
|
saved: {'favorite-detail'},
|
|
cards: [_favoriteDrama],
|
|
);
|
|
final favorites = FavoriteController(repository);
|
|
await favorites.load();
|
|
final logic = _logic(
|
|
_favoriteDrama,
|
|
FakeWatchHistory(),
|
|
favorites: favorites,
|
|
);
|
|
|
|
expect(await logic.toggleFavorite(), isTrue);
|
|
expect(logic.adjustedFavoriteCount, 1);
|
|
logic.dispose();
|
|
});
|
|
}
|
|
|
|
const _favoriteDrama = Drama(
|
|
id: 'favorite-detail',
|
|
title: 'Favorite detail',
|
|
synopsis: 'favorite count',
|
|
posterUrl: '/files/favorite-detail.jpg',
|
|
favoriteCount: 2,
|
|
detailLoaded: true,
|
|
);
|