189 lines
5.3 KiB
Dart
189 lines
5.3 KiB
Dart
// 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<void> _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<void> _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);
|
|
}
|
|
}
|