// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:provider/provider.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/remote/api_client.dart'; import 'package:fruitshorts/core/data/repositories/catalog_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/data/services/catalog_controller.dart'; import 'package:fruitshorts/core/data/services/favorite_controller.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/drama_detail/drama_detail_view.dart'; import '../support/fake_catalog.dart'; void main() { testWidgets('should_show_card_content_before_detail_returns', (tester) async { final pending = Completer(); final card = _cardDrama(); final catalog = _ScriptedCatalog( dramas: [card], loadDetail: () => pending.future, ); await _pumpDetail(tester, card: card, catalog: catalog, settle: false); expect(find.byKey(const Key('detail-title')), findsOneWidget); expect(find.byKey(const Key('detail-poster')), findsOneWidget); expect(find.byKey(const Key('detail-stats')), findsOneWidget); expect(find.byKey(const Key('detail-episodes-loading')), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsNothing); expect( tester .widget(find.byKey(const Key('detail-cta'))) .onPressed, isNull, ); pending.complete(_detailDrama(card)); await tester.pump(); await tester.pumpAndSettle(); expect(find.byKey(const Key('detail-episodes-loading')), findsNothing); expect(find.byKey(const Key('detail-episode-chips')), findsOneWidget); expect( tester .widget(find.byKey(const Key('detail-cta'))) .onPressed, isNotNull, ); }); testWidgets('should_keep_card_content_when_detail_load_fails', ( tester, ) async { final card = _cardDrama(); final catalog = _ScriptedCatalog( dramas: [card], loadDetail: () async => throw const ApiException(500, 'detail failed'), ); await _pumpDetail(tester, card: card, catalog: catalog); expect(find.byKey(const Key('detail-title')), findsOneWidget); expect(find.byKey(const Key('detail-poster')), findsOneWidget); expect(find.byKey(const Key('detail-stats')), findsOneWidget); expect(find.byKey(const Key('detail-episodes-retry')), findsOneWidget); expect(find.byType(CircularProgressIndicator), findsNothing); expect( tester .widget(find.byKey(const Key('detail-cta'))) .onPressed, isNull, ); }); } Future _pumpDetail( WidgetTester tester, { required Drama card, required CatalogRepository catalog, bool settle = true, }) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final favorites = FavoriteController( InMemoryFavoriteRepository(cards: [card]), ); await favorites.load(); await tester.pumpWidget( MultiProvider( providers: [ Provider.value( value: InMemoryWatchHistoryRepository(), ), Provider.value(value: FakePlaybackRepository()), ChangeNotifierProvider.value(value: favorites), ChangeNotifierProvider( create: (_) => CatalogController(catalog)..load(), ), ], child: MaterialApp( locale: const Locale('en'), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, home: DramaDetailPage(drama: card), ), ), ); if (settle) { await tester.pumpAndSettle(); } else { await tester.pump(); } } Drama _cardDrama() => const Drama( id: 'detail-k53', title: 'Detail K53', synopsis: 'A card synopsis is available before the detail request finishes.', posterUrl: '/files/detail-k53.png', category: 'Romance', genre: 'Urban', language: 'id', totalEpisodes: 8, viewCount: 120000, favoriteCount: 3200, likeCount: 5600, ); Drama _detailDrama(Drama card) => Drama( id: card.id, title: card.title, synopsis: card.synopsis, posterUrl: card.posterUrl, category: card.category, genre: card.genre, language: card.language, totalEpisodes: card.totalEpisodes, viewCount: card.viewCount, favoriteCount: card.favoriteCount, likeCount: card.likeCount, detailLoaded: true, episodes: [ for (var number = 1; number <= card.totalEpisodes; number++) Episode( id: '${card.id}-$number', number: number, title: 'Episode $number', isFree: number == 1, videoUrl: number == 1 ? '/files/${card.id}-1.mp4' : '', locked: number > 1, likeCount: card.likeCount, commentCount: card.commentCount, ), ], ); class _ScriptedCatalog extends FakeCatalogRepository { _ScriptedCatalog({required List dramas, required this.loadDetail}) : super(dramas: dramas); final Future Function() loadDetail; @override Future fetchDrama(String dramaId) => loadDetail(); }