// 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/home_feed.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; import 'package:fruitshorts/core/widgets/app_pull_to_refresh.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: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_haptics.dart'; import '../support/fake_interaction_repositories.dart'; import '../support/widget_player.dart'; void main() { setUp(() { SharedPreferencesAsyncPlatform.instance = InMemorySharedPreferencesAsync.empty(); }); testWidgets('should_open_ranking_sorted_by_views', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('ranking-page')), findsOneWidget); _expectUnifiedHeader(tester, const Key('ranking-back')); expect(find.text('TOP 1'), findsOneWidget); expect( tester.widget(find.byKey(const Key('ranking-title-1'))).data, 'The Signal', ); await tester.tap(find.byKey(const Key('ranking-top-1'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('player-scaffold')), findsOneWidget); }); testWidgets('should_open_new_releases_sorted_by_id', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-new'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('new-releases-page')), findsOneWidget); _expectUnifiedHeader(tester, const Key('new-releases-back')); final newPage = tester.widget( find.byKey(const Key('new-releases-page')), ); expect(newPage.backgroundColor, AppColors.ink); final badge = tester.widget( find.byKey(const Key('new-releases-hero-badge')), ); expect((badge.decoration! as BoxDecoration).color, AppColors.blue); expect(find.byKey(const Key('new-releases-hero')), findsOneWidget); expect(find.text('Vengeance Rising'), findsWidgets); expect( find.byKey(const Key('new-release-card-the-signal-demo')), findsOneWidget, ); expect( find.byKey(const Key('new-release-card-city-lights-demo')), findsOneWidget, ); await tester.tap(find.byKey(const Key('new-releases-hero'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('player-scaffold')), findsOneWidget); }); testWidgets('should_open_reserve_empty_state', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-reserve'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('reserve-page')), findsOneWidget); _expectUnifiedHeader(tester, const Key('reserve-back')); expect(find.byKey(const Key('reserve-week-strip')), findsOneWidget); for (var index = 0; index < 7; index++) { expect(find.byKey(Key('reserve-day-$index')), findsOneWidget); } expect(find.byKey(const Key('reserve-empty')), findsOneWidget); for (var index = 1; index <= 3; index++) { expect(find.byKey(Key('reserve-step-$index')), findsOneWidget); } final todayIndex = DateTime.now().weekday - 1; final today = tester.widget( find.descendant( of: find.byKey(Key('reserve-day-$todayIndex')), matching: find.byType(Container), ), ); expect((today.decoration! as BoxDecoration).color, AppColors.blue); await tester.tap(find.byKey(const Key('reserve-go-new'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('new-releases-page')), findsOneWidget); }); testWidgets('should_render_podium_with_center_on_top', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); final top1 = tester.getRect(find.byKey(const Key('ranking-cover-1'))); final top2 = tester.getRect(find.byKey(const Key('ranking-cover-2'))); final top3 = tester.getRect(find.byKey(const Key('ranking-cover-3'))); expect(top1.width, greaterThan(top2.width)); expect(top3.width, closeTo(top2.width, 0.5)); expect(top2.left, closeTo(14, 0.5)); expect(top3.right, closeTo(376, 0.5)); expect(top2.left, lessThan(top1.left)); expect(top3.left, greaterThan(top1.right)); expect(top1.bottom, closeTo(top2.bottom, 0.5)); expect(top1.bottom, closeTo(top3.bottom, 0.5)); expect(find.byKey(const Key('ranking-row-3')), findsNothing); }); testWidgets('should_list_rank_four_and_below', (tester) async { await _pumpTheater(tester, dramas: _rankDramas(5)); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('ranking-row-3')), findsOneWidget); expect(find.byKey(const Key('ranking-row-2')), findsNothing); expect(find.byKey(const Key('ranking-top-4')), findsOneWidget); expect( find.descendant( of: find.byKey(const Key('ranking-row-3')), matching: find.text('synopsis'), ), findsOneWidget, ); }); testWidgets('should_truncate_podium_when_less_than_three', (tester) async { await _pumpTheater(tester, dramas: _rankDramas(1)); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('ranking-top-1')), findsOneWidget); expect(find.byKey(const Key('ranking-top-2')), findsNothing); expect(find.byKey(const Key('ranking-top-3')), findsNothing); }); testWidgets('should_open_player_from_new_releases_card', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-new'))); await tester.pumpAndSettle(); final card = find.byKey(const Key('new-release-card-the-signal-demo')); await tester.ensureVisible(card); await tester.pumpAndSettle(); await tester.tap(card); await tester.pumpAndSettle(); expect(find.byKey(const Key('player-scaffold')), findsOneWidget); }); testWidgets('should_go_back_to_theater_from_ranking_page', (tester) async { await _pumpTheater(tester); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('ranking-back'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('ranking-page')), findsNothing); expect(find.byKey(const Key('discover-search')), findsOneWidget); }); testWidgets('should_page_ranking_and_reset_after_refresh', (tester) async { final repository = _CountingCatalogRepository(dramas: _rankDramas(40)); await _pumpTheater(tester, catalogRepository: repository); await tester.tap(find.byKey(const Key('theater-toolbar-ranking'))); await tester.pumpAndSettle(); final list = find.byKey(const Key('ranking-list')); expect(tester.widget(list).physics, AppPullToRefresh.physics); final position = _scrollPosition(tester, const Key('ranking-list')); position.jumpTo(position.maxScrollExtent); await tester.pump(); expect(find.byKey(const Key('ranking-row-19')), findsOneWidget); expect(find.byKey(const Key('ranking-row-20')), findsNothing); expect(find.byKey(const Key('ranking-load-more')), findsOneWidget); await tester.pump(const Duration(milliseconds: 299)); expect(find.byKey(const Key('ranking-load-more')), findsOneWidget); await tester.pump(const Duration(milliseconds: 1)); position.jumpTo(position.maxScrollExtent); await tester.pump(); expect(find.byKey(const Key('ranking-row-39')), findsOneWidget); expect(find.byKey(const Key('ranking-row-40')), findsNothing); expect(find.byKey(const Key('ranking-no-more')), findsOneWidget); position.jumpTo(0); await tester.pump(); final beforeRefresh = repository.fetchCalls; await tester.drag(list, const Offset(0, 240)); await tester.pump(); await tester.pumpAndSettle(); expect(repository.fetchCalls, beforeRefresh + 1); expect(position.pixels, 0); position.jumpTo(position.maxScrollExtent); await tester.pump(); expect(find.byKey(const Key('ranking-load-more')), findsOneWidget); expect(find.byKey(const Key('ranking-no-more')), findsNothing); await tester.pumpAndSettle(); }); testWidgets('should_page_new_releases_and_reset_after_refresh', ( tester, ) async { final repository = _CountingCatalogRepository(dramas: _rankDramas(40)); await _pumpTheater(tester, catalogRepository: repository); await tester.tap(find.byKey(const Key('theater-toolbar-new'))); await tester.pumpAndSettle(); final list = find.byKey(const Key('new-releases-list')); expect(tester.widget(list).physics, AppPullToRefresh.physics); await tester.drag(list, const Offset(0, -400)); await tester.pump(); expect(_countKeys('home-drama-card-'), 20); final position = _scrollPosition(tester, const Key('new-releases-list')); position.jumpTo(position.maxScrollExtent); await tester.pump(); expect(find.byKey(const Key('new-releases-load-more')), findsOneWidget); await tester.pump(const Duration(milliseconds: 299)); expect(_countKeys('home-drama-card-'), 20); await tester.pump(const Duration(milliseconds: 1)); await tester.pump(); expect(_countKeys('home-drama-card-'), 40); await tester.pumpAndSettle(); position.jumpTo(position.maxScrollExtent); await tester.pumpAndSettle(); expect(find.byKey(const Key('new-releases-no-more')), findsOneWidget); position.jumpTo(0); await tester.pump(); final beforeRefresh = repository.fetchCalls; await tester.drag(list, const Offset(0, 240)); await tester.pump(); await tester.pumpAndSettle(); expect(repository.fetchCalls, beforeRefresh + 1); expect(position.pixels, 0); await tester.drag(list, const Offset(0, -400)); await tester.pump(); expect(_countKeys('home-drama-card-'), 20); }); testWidgets('should_refresh_reserve_without_load_more', (tester) async { final repository = _CountingCatalogRepository(dramas: _rankDramas(3)); await _pumpTheater(tester, catalogRepository: repository); await tester.tap(find.byKey(const Key('theater-toolbar-reserve'))); await tester.pumpAndSettle(); final beforeRefresh = repository.fetchCalls; final list = find.byKey(const Key('reserve-list')); expect(tester.widget(list).physics, AppPullToRefresh.physics); await tester.drag(list, const Offset(0, 240)); await tester.pump(); await tester.pumpAndSettle(); expect(repository.fetchCalls, beforeRefresh + 1); expect(find.byKey(const Key('reserve-load-more')), findsNothing); expect(find.byKey(const Key('reserve-no-more')), findsNothing); }); } Future _pumpTheater( WidgetTester tester, { List? dramas, FakeCatalogRepository? catalogRepository, }) async { tester.view.physicalSize = const Size(390, 844); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final catalog = catalogRepository ?? FakeCatalogRepository(dramas: dramas); await tester.pumpWidget( LumaDramaApp( playerFactory: createWidgetPlayer, catalogRepository: catalog, searchRepository: catalog, categoryRepository: FakeCategoryRepository(catalog: catalog), watchHistoryRepository: InMemoryWatchHistoryRepository(), favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas), playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), interactionRepository: FakeInteractionRepository(), haptics: FakeHaptics(), ), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); } ScrollPosition _scrollPosition(WidgetTester tester, Key listKey) { final scrollable = find.descendant( of: find.byKey(listKey), matching: find.byType(Scrollable), ); return tester.state(scrollable.first).position; } int _countKeys(String prefix) => find .byWidgetPredicate((widget) { final key = widget.key; return key is ValueKey && key.value.startsWith(prefix); }) .evaluate() .length; class _CountingCatalogRepository extends FakeCatalogRepository { _CountingCatalogRepository({super.dramas}); int fetchCalls = 0; @override Future fetchHomeFeed() { fetchCalls++; return super.fetchHomeFeed(); } } List _rankDramas(int count) => [ for (var index = 0; index < count; index++) Drama( id: 'rank-$index', title: 'Rank $index', synopsis: 'synopsis', posterUrl: '', totalEpisodes: 10, viewCount: (count - index) * 100, episodes: [ Episode( id: 'rank-$index-1', number: 1, title: 'Episode 1', isFree: true, ), ], ), ]; void _expectUnifiedHeader(WidgetTester tester, Key backKey) { expect(tester.getSize(find.byKey(backKey)), const Size(40, 40)); final title = tester.widget(find.byKey(const Key('subpage-title'))); expect(title.style?.fontSize, 18); expect(title.style?.fontWeight, FontWeight.w800); }