// 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/home_feed.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/pages/player/player_view.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_render_four_tabs_and_open_create_placeholder', ( tester, ) async { await _pumpApp(tester); for (final key in _tabKeys) { expect(find.byKey(key), findsOneWidget); } await tester.tap(find.byKey(const Key('tab-create'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('create-page')), findsOneWidget); expect(find.text('Create'), findsWidgets); expect(find.text('Stories become short dramas'), findsOneWidget); expect(find.byKey(const Key('create-ai')), findsOneWidget); expect(find.byKey(const Key('create-upload')), findsOneWidget); await tester.tap(find.byKey(const Key('create-ai'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('create-placeholder-page')), findsOneWidget); expect(find.text('Coming soon'), findsOneWidget); }); testWidgets('should_switch_home_channel_when_underline_tab_is_tapped', ( tester, ) async { await _pumpApp(tester); // 选中态用主题色,未选中保留白色文字与透明占位。 expect(_underlineColor(tester, 'hot'), AppColors.blue); expect(_underlineColor(tester, 'free'), Colors.transparent); expect(_labelStyle(tester, 'hot').fontWeight, FontWeight.w800); expect(_labelStyle(tester, 'free').fontWeight, FontWeight.w600); expect(_labelStyle(tester, 'hot').color, AppColors.blue); expect(_labelStyle(tester, 'free').color, Colors.white70); await tester.tap(find.byKey(const Key('home-channel-free'))); await tester.pumpAndSettle(); expect(_underlineColor(tester, 'hot'), Colors.transparent); expect(_underlineColor(tester, 'free'), AppColors.blue); expect(_labelStyle(tester, 'free').color, AppColors.blue); expect(_labelStyle(tester, 'hot').color, Colors.white70); expect(_labelStyle(tester, 'free').fontWeight, FontWeight.w800); }); testWidgets('should_render_flush_hero_with_channels_overlaid', ( tester, ) async { await _pumpApp(tester); final hero = tester.getRect(find.byKey(const Key('home-hero-banner'))); expect(hero.width, 390); expect(hero.left, 0); final card = tester.widget( find .descendant( of: find.byKey(const Key('home-hero-banner')), matching: find.byType(ClipRRect), ) .first, ); expect(card.borderRadius, BorderRadius.zero); // 频道行在 hero Stack 内、位于轮播顶部 8 的位置上。 expect( find.ancestor( of: find.byKey(const Key('home-channels')), matching: find.byKey(const Key('home-hero-section')), ), findsOneWidget, ); final channels = tester.getRect(find.byKey(const Key('home-channels'))); expect(channels.top, closeTo(hero.top + 8, 1)); }); testWidgets('should_open_theater_search_when_home_search_is_tapped', ( tester, ) async { await _pumpApp(tester); await tester.tap(find.byKey(const Key('home-search'))); await tester.pumpAndSettle(); final field = tester.widget( find.byKey(const Key('discover-search')), ); expect(field.focusNode?.hasFocus, isTrue); }); testWidgets('should_not_render_theater_language_filters', (tester) async { await _pumpApp(tester); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('theater-language-chips')), findsNothing); expect(find.byKey(const Key('theater-category-chips')), findsOneWidget); expect(find.byKey(const Key('discover-search')), findsOneWidget); expect(find.byKey(const Key('discover-refresh')), findsOneWidget); expect( find.byKey(const Key('home-drama-card-the-signal-demo')), findsOneWidget, ); }); testWidgets('should_open_fullscreen_player_when_home_card_is_tapped', ( tester, ) async { await _pumpApp(tester); await tester.ensureVisible( find.byKey(const Key('home-drama-card-the-signal-demo')), ); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('home-drama-card-the-signal-demo'))); await tester.pumpAndSettle(); final player = tester.widget(find.byType(PlayerPage)); expect(player.homeMode, isFalse); }); testWidgets('should_use_first_drama_cover_when_home_has_no_banner', ( tester, ) async { await _pumpApp(tester, withBanner: false); expect(find.byKey(const Key('home-hero-banner')), findsOneWidget); expect(find.byKey(const Key('home-hero-title-0')), findsOneWidget); await tester.tap(find.byKey(const Key('feed-banner-0'))); await tester.pumpAndSettle(); final player = tester.widget(find.byType(PlayerPage)); expect(player.homeMode, isFalse); }); testWidgets('should_open_fullscreen_player_when_theater_card_is_tapped', ( tester, ) async { await _pumpApp(tester); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); final card = find.byKey(const Key('home-drama-card-the-signal-demo')); await tester.scrollUntilVisible( card, 220, scrollable: find .descendant( of: find.byKey(const Key('discover-refresh')), matching: find.byType(Scrollable), ) .first, ); await tester.ensureVisible(card); await tester.pumpAndSettle(); await tester.tap(card); await tester.pumpAndSettle(); final player = tester.widget(find.byType(PlayerPage)); expect(player.homeMode, isFalse); }); testWidgets('should_resume_continue_watching_at_saved_episode', ( tester, ) async { await _pumpApp( tester, history: InMemoryWatchHistoryRepository( seed: const [ WatchProgress( dramaId: 'the-signal-demo', episodeId: 'the-signal-demo-3', position: Duration(minutes: 2), duration: Duration(minutes: 10), ), ], ), ); expect(find.byKey(const Key('home-continue-watching')), findsOneWidget); expect(find.text('Episode 3 · 8 min left'), findsOneWidget); await tester.tap( find.byKey(const Key('home-continue-play-the-signal-demo')), ); await tester.pumpAndSettle(); final player = tester.widget(find.byType(PlayerPage)); expect(player.homeMode, isFalse); expect(player.initialIndex, 2); }); testWidgets('should_fit_home_and_theater_on_320_width', (tester) async { await _pumpApp(tester, size: const Size(320, 568)); expect(find.byKey(const Key('home-search')), findsOneWidget); expect(find.byKey(const Key('home-hero-title-0')), findsOneWidget); expect(tester.takeException(), isNull); await tester.tap(find.byKey(const Key('tab-theater'))); await tester.pumpAndSettle(); expect(find.byKey(const Key('theater-category-chips')), findsOneWidget); expect(find.byKey(const Key('theater-language-chips')), findsNothing); expect(tester.takeException(), isNull); }); } const _tabKeys = [ Key('tab-home'), Key('tab-theater'), Key('tab-create'), Key('tab-mine'), ]; Color? _underlineColor(WidgetTester tester, String name) { final underline = tester.widget( find.byKey(Key('home-channel-underline-$name')), ); return (underline.decoration! as BoxDecoration).color; } TextStyle _labelStyle(WidgetTester tester, String name) { final label = tester.widget( find.descendant( of: find.byKey(Key('home-channel-$name')), matching: find.byType(Text), ), ); return label.style!; } Future _pumpApp( WidgetTester tester, { WatchHistoryRepository? history, Size size = const Size(390, 844), bool withBanner = true, }) async { tester.view.physicalSize = size; tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); final catalog = FakeCatalogRepository( banners: withBanner ? const [ FeedBanner( id: 'banner-1', imageUrl: '/files/banner.jpg', linkType: 'DRAMA', linkValue: 'the-signal-demo', ), ] : const [], ); await tester.pumpWidget( LumaDramaApp( playerFactory: createWidgetPlayer, catalogRepository: catalog, categoryRepository: FakeCategoryRepository(catalog: catalog), watchHistoryRepository: history ?? InMemoryWatchHistoryRepository(), favoriteRepository: InMemoryFavoriteRepository(cards: catalog.dramas), playbackRepository: FakePlaybackRepository(), commentRepository: FakeCommentRepository(), interactionRepository: FakeInteractionRepository(), haptics: FakeHaptics(), ), ); await tester.pumpAndSettle(); }