import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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 'package:fruitshorts/app.dart'; import 'package:fruitshorts/core/data/models/content_category.dart'; import 'package:fruitshorts/core/data/models/drama.dart'; import 'package:fruitshorts/core/data/models/home_feed.dart'; import 'package:fruitshorts/core/data/repositories/category_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/widgets/remote_image.dart'; import '../support/fake_catalog.dart'; import '../support/fake_interaction_repositories.dart'; import '../support/fake_notice_repository.dart'; import '../support/widget_player.dart'; void main() { setUp(() { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); }); testWidgets( 'should_show_all_dramas_in_three_column_sliver_grid_without_continue_watching', (tester) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await _pumpApp(tester, categoryRepository: _GridCategoryRepository()); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle(); expect(find.text('Continue Watching'), findsNothing); expect(find.byKey(const Key('home-all-dramas-title')), findsNothing); expect(find.text('All'), findsOneWidget); expect(find.text('For You'), findsNothing); expect( tester.getCenter(find.text('All')).dx, lessThan(tester.getCenter(find.text('Hot')).dx), ); expect( tester .getTopLeft( find.byKey(const Key('home-drama-card-the-signal-demo')), ) .dy - tester .getBottomLeft(find.byKey(const Key('home-category-chips'))) .dy, closeTo(14, 0.1), ); final grid = tester.widget( find.byKey(const Key('home-all-dramas-grid')), ); final delegate = grid.gridDelegate as SliverGridDelegateWithFixedCrossAxisCount; expect(delegate.crossAxisCount, 3); expect(delegate.crossAxisSpacing, 10); expect(delegate.mainAxisSpacing, 14); expect(delegate.childAspectRatio, closeTo(2 / 3, 0.001)); expect( tester .widget( find.byKey(const Key('home-all-dramas-section')), ) .padding, const EdgeInsets.symmetric(horizontal: 16), ); expect(find.byType(GridView), findsNothing); expect(find.byType(CustomScrollView), findsOneWidget); expect(find.byType(RemoteImage), findsWidgets); final cover = tester.widget( find.byKey(const Key('home-drama-cover-city-lights-demo')), ); expect(cover.aspectRatio, closeTo(2 / 3, 0.001)); expect( tester .widget( find.byKey(const Key('home-drama-title-city-lights-demo')), ) .maxLines, 2, ); expect( tester .widget( find.byKey(const Key('home-drama-genre-city-lights-demo')), ) .maxLines, 1, ); expect(tester.takeException(), isNull); }, ); testWidgets('should_update_grid_when_category_is_selected', (tester) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await _pumpApp(tester, categoryRepository: _GridCategoryRepository()); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle(); expect(find.text('The Signal'), findsOneWidget); expect(find.text('City Lights & Velvet Nights'), findsOneWidget); await tester.tap(find.text('Romance')); await tester.pumpAndSettle(); expect(find.text('The Signal'), findsNothing); expect(find.text('City Lights & Velvet Nights'), findsOneWidget); expect( find.byKey(const Key('home-drama-card-the-signal-demo')), findsNothing, ); expect( find.byKey(const Key('home-drama-card-city-lights-demo')), findsOneWidget, ); }); testWidgets('should_keep_empty_messages_when_feed_has_no_dramas', ( tester, ) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await _pumpApp( tester, categoryRepository: _GridCategoryRepository( discoverFeed: const HomeFeed(), ), ); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-all-dramas-title')), findsNothing); expect(find.text('No matching dramas.'), findsOneWidget); await tester.tap(find.text('Romance')); await tester.pumpAndSettle(); expect(find.text('No dramas in this category yet.'), findsOneWidget); }); testWidgets('should_show_retry_when_category_feed_fails', (tester) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await _pumpApp(tester, categoryRepository: _FailingCategoryRepository()); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-all-dramas-title')), findsNothing); expect(find.text('Categories could not be loaded.'), findsOneWidget); expect(find.text('Retry'), findsOneWidget); expect(find.byKey(const Key('home-all-dramas-grid')), findsNothing); }); testWidgets('should_fit_three_column_grid_on_compact_screen', (tester) async { tester.view.physicalSize = const Size(320, 568); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); await _pumpApp(tester, categoryRepository: _GridCategoryRepository()); await tester.tap(find.byKey(const Key('tab-discover'))); await tester.pumpAndSettle(); await tester.scrollUntilVisible( find.byKey(const Key('home-drama-card-city-lights-demo')), 180, scrollable: find.byType(Scrollable).last, ); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-all-dramas-title')), findsNothing); expect(find.text('City Lights & Velvet Nights'), findsOneWidget); expect(tester.takeException(), isNull); }); } Future _pumpApp( WidgetTester tester, { required CategoryRepository categoryRepository, }) async { final catalog = FakeCatalogRepository(); await tester.pumpWidget( LumaDramaApp( playerFactory: createWidgetPlayer, catalogRepository: catalog, categoryRepository: categoryRepository, watchHistoryRepository: InMemoryWatchHistoryRepository(), favoriteRepository: InMemoryFavoriteRepository(), playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), interactionRepository: FakeInteractionRepository(), noticeRepository: FakeNoticeRepository(), ), ); await tester.pumpAndSettle(); } class _GridCategoryRepository implements CategoryRepository { _GridCategoryRepository({HomeFeed? discoverFeed}) : discoverFeed = discoverFeed ?? _defaultDiscoverFeed; final HomeFeed discoverFeed; @override Future> fetchCategories() async => const [ ContentCategory(id: '7', name: 'Romance'), ]; @override Future fetchDiscoverFeed() async => discoverFeed; } class _FailingCategoryRepository implements CategoryRepository { @override Future> fetchCategories() async => const [ ContentCategory(id: '7', name: 'Romance'), ]; @override Future fetchDiscoverFeed() async { throw StateError('discover feed unavailable'); } } final _defaultDiscoverFeed = HomeFeed( sections: [ FeedSection(code: 'hot', title: 'Hot', dramas: demoDramas), FeedSection( code: 'category-7', title: 'Romance', dramas: [ Drama( id: 'city-lights-demo', title: 'City Lights & Velvet Nights', synopsis: 'City Lights & Velvet Nights synopsis', posterUrl: '/files/city-lights-demo.png', genre: 'Urban Romance', detailLoaded: true, ), ], ), ], );