// 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/models/watch_progress.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/theme/app_colors.dart'; import 'package:fruitshorts/core/theme/app_dimens.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_catalog.dart'; import '../support/fake_category.dart'; import '../support/fake_interaction_repositories.dart'; import '../support/widget_player.dart'; void main() { testWidgets('should_render_following_tabs_grid_and_edit_actions', ( tester, ) async { _signIn(); final catalog = FakeCatalogRepository(); await tester.pumpWidget( _app( catalog: catalog, favorites: InMemoryFavoriteRepository( cards: catalog.dramas.take(1).toList(), saved: {catalog.dramas.first.id}, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('following-page')), findsOneWidget); expect(find.byKey(const Key('following-tab-favorites')), findsOneWidget); expect(find.byKey(const Key('following-tab-history')), findsOneWidget); expect(find.byKey(const Key('following-tab-reserve')), findsNothing); expect(find.byKey(const Key('following-favorites-grid')), findsOneWidget); expect(find.byKey(const Key('following-add-card')), findsOneWidget); expect(find.byType(CustomScrollView), findsWidgets); final tab = tester.widget( find.descendant( of: find.byKey(const Key('following-tab-favorites')), matching: find.byType(AnimatedDefaultTextStyle), ), ); expect(tab.style.fontSize, AppDimens.followingTabFont); expect(tab.style.fontWeight, FontWeight.w600); expect(tab.style.color, AppColors.textPrimary); final editIcon = tester.widget(find.byIcon(Icons.edit_outlined)); expect(editIcon.size, AppDimens.followingEditIconSize); expect(editIcon.color, AppColors.textTabInactive); final grid = tester.widget( find.descendant( of: find.byKey(const Key('following-favorites-grid')), matching: find.byType(SliverGrid), ), ); final delegate = grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 3); expect(delegate.crossAxisSpacing, AppDimens.followingGridGap); expect(delegate.mainAxisSpacing, AppDimens.followingGridRowGap); expect(delegate.mainAxisExtent, greaterThan(0)); final card = find.byKey(const Key('following-card-the-signal-demo')); final coverFinder = find.byKey( const Key('following-cover-the-signal-demo'), ); final cover = tester.widget(coverFinder); expect(cover.aspectRatio, AppDimens.cardCoverAspectRatio); final coverClip = tester.widget( find.descendant( of: find.byKey(const Key('following-cover-the-signal-demo')), matching: find.byType(ClipRRect), ), ); expect( coverClip.borderRadius, BorderRadius.circular(AppDimens.homeCardRadius), ); final titleFinder = find.descendant( of: card, matching: find.text('The Signal'), ); final title = tester.widget(titleFinder); expect(title.style?.fontSize, AppDimens.cardTitleFont); expect(title.style?.fontWeight, FontWeight.w500); expect(title.style?.height, AppDimens.cardTitleLineHeight); final views = tester.widget( find.descendant( of: find.byKey(const Key('following-views-the-signal-demo')), matching: find.byType(Text), ), ); expect(views.style?.fontSize, AppDimens.cardPlayCountFont); expect(views.style?.fontWeight, FontWeight.w400); expect( find.descendant( of: coverFinder, matching: find.byKey(const Key('following-views-the-signal-demo')), ), findsOneWidget, ); expect(views.style?.color, AppColors.textPrimary); expect( find.descendant( of: find.byKey(const Key('following-views-the-signal-demo')), matching: find.byIcon(Icons.local_fire_department_rounded), ), findsOneWidget, ); expect( tester.getTopLeft(titleFinder).dx, tester.getTopLeft(coverFinder).dx, ); expect( tester.getTopLeft(card).dx, lessThan( tester.getTopLeft(find.byKey(const Key('following-add-card'))).dx, ), ); await tester.tap(find.byKey(const Key('following-edit'))); await tester.pumpAndSettle(); expect( find.byKey(const Key('following-remove-the-signal-demo')), findsOneWidget, ); await tester.tap(find.byKey(const Key('following-remove-the-signal-demo'))); await tester.pumpAndSettle(); expect(find.byType(AlertDialog), findsOneWidget); await tester.tap( find.descendant( of: find.byType(AlertDialog), matching: find.byType(FilledButton), ), ); await tester.pumpAndSettle(); expect( find.byKey(const Key('following-remove-the-signal-demo')), findsNothing, ); expect(find.byIcon(Icons.edit_outlined), findsOneWidget); expect( find.byKey(const Key('following-card-the-signal-demo')), findsNothing, ); await tester.tap(find.byKey(const Key('following-tab-history'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('following-history-empty')), findsOneWidget); }); testWidgets('should_show_auth_gate_when_signed_out', (tester) async { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); await tester.pumpWidget(_app(catalog: FakeCatalogRepository())); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('auth-gate-panel')), findsOneWidget); expect(find.byKey(const Key('following-favorites-grid')), findsNothing); }); testWidgets('should_show_empty_favorites_and_history', (tester) async { _signIn(); await tester.pumpWidget(_app(catalog: FakeCatalogRepository())); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('following-add-card')), findsOneWidget); expect(find.byKey(const Key('following-favorites-empty')), findsNothing); await tester.tap(find.byKey(const Key('following-tab-history'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('following-history-empty')), findsOneWidget); }); testWidgets('should_render_history_metadata_without_detail_fetch', ( tester, ) async { _signIn(); final catalog = _CountingCatalogRepository(); await tester.pumpWidget( _app( catalog: catalog, history: InMemoryWatchHistoryRepository( seed: [ WatchProgress( dramaId: 'the-signal-demo', episodeId: 'the-signal-demo-3', dramaTitle: 'Server title', dramaCover: '/files/server-cover.jpg', episodeNumber: 3, totalEpisodes: 8, position: const Duration(seconds: 25), duration: const Duration(seconds: 100), ), ], ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('following-tab-history'))); await tester.pumpAndSettle(); expect(find.text('Server title'), findsOneWidget); expect(find.text('Episode 3 / 8 total'), findsOneWidget); expect(find.text('Watched 25%'), findsOneWidget); expect( find.byKey(const Key('following-history-card-the-signal-demo')), findsOneWidget, ); expect( find.byKey(const Key('following-history-continue-the-signal-demo')), findsOneWidget, ); expect( find.byKey(const Key('following-progress-the-signal-demo')), findsOneWidget, ); final cover = tester.widget( find.byKey(const Key('following-history-cover-the-signal-demo')), ); expect(cover.width, AppDimens.followingHistoryCoverWidth); expect(cover.height, AppDimens.followingHistoryCoverHeight); final historyGrid = tester.widget( find.descendant( of: find.byKey(const Key('following-history-grid')), matching: find.byType(SliverGrid), ), ); final historyDelegate = historyGrid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(historyDelegate.crossAxisCount, 1); expect(historyDelegate.mainAxisSpacing, AppDimens.followingHistoryRowGap); expect(catalog.detailFetches, 0); await tester.tap(find.byKey(const Key('following-edit'))); await tester.pumpAndSettle(); expect( find.byKey(const Key('following-history-remove-the-signal-demo')), findsOneWidget, ); expect( find.byKey(const Key('following-history-continue-the-signal-demo')), findsNothing, ); }); testWidgets('should_switch_following_pages_horizontally', (tester) async { _signIn(); await tester.pumpWidget(_app(catalog: FakeCatalogRepository())); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); await tester.drag( find.byKey(const Key('following-tab-pages')), const Offset(-500, 0), ); await tester.pumpAndSettle(); expect(find.byKey(const Key('following-history-empty')), findsOneWidget); final historyTab = tester.widget( find.descendant( of: find.byKey(const Key('following-tab-history')), matching: find.byType(AnimatedDefaultTextStyle), ), ); expect(historyTab.style.fontWeight, FontWeight.w600); expect(historyTab.style.color, AppColors.textPrimary); }); testWidgets('should_open_home_from_add_card', (tester) async { _signIn(); await tester.pumpWidget(_app(catalog: FakeCatalogRepository())); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('following-add-card'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-grid')), findsOneWidget); }); testWidgets('should_reset_edit_state_when_switching_tabs', (tester) async { _signIn(); final catalog = FakeCatalogRepository(); await tester.pumpWidget( _app( catalog: catalog, favorites: InMemoryFavoriteRepository( cards: catalog.dramas, saved: catalog.dramas.map((drama) => drama.id).toSet(), ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('following-edit'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('following-tab-history'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('following-tab-favorites'))); await tester.pumpAndSettle(); expect( find.byKey(const Key('following-card-the-signal-demo')), findsOneWidget, ); expect(find.byIcon(Icons.edit_outlined), findsOneWidget); }); testWidgets('should_fit_following_cards_on_compact_viewport', (tester) async { tester.view.physicalSize = const Size(320, 640); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); _signIn(); final catalog = FakeCatalogRepository(); await tester.pumpWidget( _app( catalog: catalog, favorites: InMemoryFavoriteRepository( cards: catalog.dramas.take(1).toList(), saved: {catalog.dramas.first.id}, ), ), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-follow'))); await tester.pumpAndSettle(); expect( find.byKey(const Key('following-card-the-signal-demo')), findsOneWidget, ); expect(tester.takeException(), isNull); }); } void _signIn() { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.withData({ 'auth.accessToken': 'access', 'auth.refreshToken': 'refresh', 'auth.user': '{"uid":"9","nickname":"Viewer","avatarUrl":""}', }); } Widget _app({ required FakeCatalogRepository catalog, FavoriteRepository? favorites, WatchHistoryRepository? history, }) { return LumaDramaApp( playerFactory: createWidgetPlayer, catalogRepository: catalog, categoryRepository: FakeCategoryRepository(catalog: catalog), watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(), favoriteRepository: favorites ?? InMemoryFavoriteRepository(), playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), interactionRepository: FakeInteractionRepository(), ); } class _CountingCatalogRepository extends FakeCatalogRepository { int detailFetches = 0; @override Future fetchDrama(String dramaId) { detailFetches += 1; return super.fetchDrama(dramaId); } }