// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/data/services/interaction_controller.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/l10n/app_localizations.dart'; import 'package:fruitshorts/pages/player/widgets/home_feed_overlay.dart'; import '../support/fake_catalog.dart'; import '../support/fake_haptics.dart'; import '../support/fake_interaction_repositories.dart'; void main() { testWidgets('double tap within 400ms never pauses', (tester) async { for (final delay in [120, 250, 300, 380]) { final repository = FakeInteractionRepository(); final haptics = FakeHaptics(); final key = GlobalKey<_HarnessState>(); final clock = _TestClock(); await _pumpHarness(tester, repository, haptics, key, clock); final center = tester.getCenter( find.byKey(const Key('home-feed-gesture')), ); await tester.tapAt(center); clock.advance(Duration(milliseconds: delay)); await tester.pump(Duration(milliseconds: delay)); await tester.tapAt(center); await tester.pump(); clock.advance(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect( key.currentState!.pauseToggles, 0, reason: 'double tap with $delay ms gap paused playback', ); expect(repository.likeTargets, hasLength(1)); expect(haptics.lightImpacts, 1); expect( find.descendant( of: find.byKey(const Key('rail-like')), matching: find.byIcon(Icons.favorite_rounded), ), findsOneWidget, ); await tester.pumpAndSettle(); } }); testWidgets('taps 500ms apart toggle playback twice', (tester) async { final key = GlobalKey<_HarnessState>(); final clock = _TestClock(); await _pumpHarness( tester, FakeInteractionRepository(), FakeHaptics(), key, clock, ); await tester.tapAt( tester.getCenter(find.byKey(const Key('home-feed-gesture'))), ); clock.advance(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect(key.currentState!.pauseToggles, 1); await tester.tapAt( tester.getCenter(find.byKey(const Key('home-feed-gesture'))), ); clock.advance(const Duration(milliseconds: 500)); await tester.pump(const Duration(milliseconds: 500)); expect(key.currentState!.pauseToggles, 2); }); } Future _pumpHarness( WidgetTester tester, FakeInteractionRepository repository, FakeHaptics haptics, GlobalKey<_HarnessState> key, _TestClock clock, ) async { await tester.pumpWidget( MaterialApp( theme: AppTheme.dark(), locale: const Locale('en'), supportedLocales: AppLocalizations.supportedLocales, localizationsDelegates: AppLocalizations.localizationsDelegates, home: _Harness( key: key, repository: repository, haptics: haptics, clock: clock, ), ), ); await tester.pumpAndSettle(); } class _Harness extends StatefulWidget { const _Harness({ super.key, required this.repository, required this.haptics, required this.clock, }); final FakeInteractionRepository repository; final FakeHaptics haptics; final _TestClock clock; @override State<_Harness> createState() => _HarnessState(); } class _HarnessState extends State<_Harness> { late final InteractionController interactions = InteractionController( widget.repository, ); int pauseToggles = 0; Future _like({bool likeOnly = false}) async { await widget.haptics.lightImpact(); final episodeId = demoDramas.first.episodes.first.id; if (interactions.isEpisodeBusy(episodeId) || (likeOnly && interactions.isEpisodeLiked(episodeId))) { return; } await interactions.toggleEpisodeLike(episodeId); } @override void dispose() { interactions.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; return Scaffold( backgroundColor: Colors.black, body: ListenableBuilder( listenable: interactions, builder: (context, _) { final episode = demoDramas.first.episodes.first; return HomeFeedOverlay( l10n: l10n, drama: demoDramas.first, episode: episode, controller: null, locked: false, liked: interactions.isEpisodeLiked(episode.id), likeCount: interactions.episodeLikeCount(episode.id), saved: false, visible: true, boosting: false, onLike: () => _like(), onDoubleTapLike: (_) => _like(likeOnly: true), now: widget.clock.now, onSave: () {}, onComments: () {}, onShare: () {}, onDetail: () {}, onOpenFullPlayer: () {}, onTogglePlay: () => setState(() => pauseToggles++), onBoostStart: () {}, onBoostEnd: () {}, onMore: () {}, ); }, ), ); } } class _TestClock { DateTime _now = DateTime(2026, 10, 22, 12); DateTime now() => _now; void advance(Duration duration) { _now = _now.add(duration); } }