// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/recommend/widgets/home_hero_carousel.dart'; const List _banners = [ FeedBanner(id: '1', imageUrl: 'https://cdn.test/a.png'), FeedBanner(id: '2', imageUrl: 'https://cdn.test/b.png'), ]; const Drama _drama = Drama( id: 'd1', title: 'City Lights', synopsis: 'A quiet city romance.', posterUrl: 'https://cdn.test/d.png', ); Widget _harness(List banners, {Drama? drama}) { return MaterialApp( locale: const Locale('en'), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, home: Scaffold( body: HomeHeroCarousel( banners: banners, dramaFor: (_) => drama, onWatch: (_) {}, ), ), ); } Color? _dotColor(WidgetTester tester, int index) { final decoration = tester .widget(find.byKey(Key('home-hero-dot-$index'))) .decoration as BoxDecoration?; return decoration?.color; } void main() { testWidgets('should_keep_looping_backwards_from_the_first_banner', ( tester, ) async { await tester.pumpWidget(_harness(_banners)); await tester.pumpAndSettle(); expect(_dotColor(tester, 0), AppColors.brandRed); // 从第一张往右滑:无缝轮播应该接到最后一张,而不是被边界挡住。 await tester.fling(find.byType(PageView), const Offset(700, 0), 1200); await tester.pumpAndSettle(); expect(_dotColor(tester, 1), AppColors.brandRed); expect(_dotColor(tester, 0), isNot(AppColors.brandRed)); }); testWidgets('should_wrap_to_first_banner_after_last_auto_advance', ( tester, ) async { await tester.pumpWidget(_harness(_banners)); await tester.pumpAndSettle(); // 自动播两次:第 2 张之后应回到第 1 张,且页码继续往前走不回头。 await tester.pump(const Duration(milliseconds: 4600)); await tester.pumpAndSettle(); expect(_dotColor(tester, 1), AppColors.brandRed); await tester.pump(const Duration(milliseconds: 4600)); await tester.pumpAndSettle(); expect(_dotColor(tester, 0), AppColors.brandRed); }); testWidgets('should_keep_only_title_and_synopsis_above_dots', (tester) async { await tester.pumpWidget(_harness(_banners, drama: _drama)); await tester.pumpAndSettle(); final copy = find.byKey(const Key('home-hero-copy-0')); expect(copy, findsOneWidget); expect( find.descendant(of: copy, matching: find.text('City Lights')), findsOneWidget, ); expect( find.descendant( of: copy, matching: find.text('A quiet city romance.'), ), findsOneWidget, ); expect(find.byKey(const Key('home-hero-watch')), findsNothing); expect(find.byKey(const Key('home-hero-favorite')), findsNothing); // 文案块整体位于轮播点上方,给进度点留出间距。 final copyRect = tester.getRect(copy); final dotRect = tester.getRect(find.byKey(const Key('home-hero-dot-0'))); expect(copyRect.bottom, lessThanOrEqualTo(dotRect.top)); }); testWidgets('should_not_loop_when_only_one_banner', (tester) async { await tester.pumpWidget( _harness(const [FeedBanner(id: '1', imageUrl: 'https://cdn.test/a.png')]), ); await tester.pumpAndSettle(); expect(find.byKey(const Key('home-hero-dot-0')), findsNothing); await tester.pump(const Duration(milliseconds: 6000)); await tester.pumpAndSettle(); expect(tester.takeException(), isNull); }); }