import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/home_feed.dart'; import 'package:fruitshorts/core/data/repositories/catalog_repository.dart'; import 'package:fruitshorts/core/data/services/catalog_controller.dart'; void main() { test('should_distinguish_empty_catalog_from_load_failure_when_retry_succeeds', () async { final repository = ScriptedCatalogRepository(); final controller = CatalogController(repository); await controller.load(); expect(controller.dramas, isEmpty); expect(controller.error, isNull); repository.fail = true; await controller.load(); expect(controller.error, isNotNull); repository.fail = false; repository.feed = HomeFeed( sections: [ FeedSection(code: 'hot', title: '热门推荐', dramas: [_card('1')]), ], ); await controller.load(); expect(controller.error, isNull); expect(controller.dramas, hasLength(1)); expect(controller.sections.single.title, '热门推荐'); }); test('should_request_drama_detail_once_when_it_is_loaded_on_demand', () async { final repository = ScriptedCatalogRepository(); final controller = CatalogController(repository); repository.feed = HomeFeed( sections: [ FeedSection(code: 'hot', title: '热门推荐', dramas: [_card('1')]), ], ); await controller.load(); final card = controller.dramas.single; expect(card.episodes, isEmpty); final detail = await controller.requireDetail(card); expect(detail.episodes, hasLength(2)); expect(repository.detailCalls, 1); // 第二次已经是详情,直接复用,不再请求 await controller.requireDetail(detail); expect(repository.detailCalls, 1); expect(controller.dramas.single.episodes, hasLength(2)); }); } Drama _card(String id) => Drama( id: id, title: 'Drama $id', synopsis: '简介 $id', posterUrl: '/files/$id.jpg', ); Drama _detail(String id) => Drama( id: id, title: 'Drama $id', synopsis: '简介 $id', posterUrl: '/files/$id.jpg', detailLoaded: true, episodes: const [ Episode(id: '1', number: 1, title: '第一集', isFree: true), Episode(id: '2', number: 2, title: '第二集', isFree: false), ], ); class ScriptedCatalogRepository implements CatalogRepository { bool fail = false; HomeFeed feed = const HomeFeed(); int detailCalls = 0; @override Future fetchHomeFeed() async { if (fail) throw StateError('offline'); return feed; } @override Future fetchDiscoverFeed() async => feed; @override Future fetchDrama(String dramaId) async { detailCalls++; return _detail(dramaId); } }