// Author: fruit.com.cn import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/pages/player/widgets/player_double_tap_hearts.dart'; void main() { testWidgets('heart pixels start at origin and move upward', (tester) async { tester.view.physicalSize = const Size(400, 800); tester.view.devicePixelRatio = 1; addTearDown(tester.view.resetPhysicalSize); addTearDown(tester.view.resetDevicePixelRatio); var completedCalls = 0; const origin = Offset(300, 546); final clock = _HeartClock(); await tester.pumpWidget( MaterialApp( home: TickerMode( enabled: false, child: Scaffold( body: Stack( fit: StackFit.expand, clipBehavior: Clip.none, children: [ Positioned.fill( child: PlayerDoubleTapHeartBurst( burstId: 91, origin: origin, now: clock.now, onCompleted: () => completedCalls++, ), ), ], ), ), ), ), ); final firstHeart = find.byKey(const Key('double-tap-heart-0')); expect(firstHeart, findsOneWidget); final firstSize = tester.getSize(firstHeart); final initialCenter = tester.getCenter(firstHeart); debugPrint( '[heart-pixels] initial size=${firstSize.width}x${firstSize.height} ' 'center=${initialCenter.dx},${initialCenter.dy}', ); expect(firstSize.width, closeTo(48, 1)); expect(firstSize.height, closeTo(48, 1)); expect(firstSize.width, greaterThan(20)); expect((initialCenter - origin).distance, lessThanOrEqualTo(1)); await _pumpHeartTime(tester, clock, const Duration(milliseconds: 300)); final center300 = tester.getCenter(firstHeart); debugPrint('[heart-pixels] t=300 center=${center300.dx},${center300.dy}'); expect(center300.dy, lessThan(initialCenter.dy)); expect((center300.dx - origin.dx).abs(), lessThanOrEqualTo(30)); await _pumpHeartTime(tester, clock, const Duration(milliseconds: 300)); final center600 = tester.getCenter(firstHeart); debugPrint('[heart-pixels] t=600 center=${center600.dx},${center600.dy}'); expect(center600.dy, lessThan(center300.dy)); expect((center600.dx - origin.dx).abs(), lessThanOrEqualTo(30)); await _pumpHeartTime(tester, clock, const Duration(milliseconds: 300)); final center900 = tester.getCenter(firstHeart); debugPrint('[heart-pixels] t=900 center=${center900.dx},${center900.dy}'); expect(center900.dy, lessThan(center600.dy)); expect((center900.dx - origin.dx).abs(), lessThanOrEqualTo(30)); expect({ initialCenter.dy, center300.dy, center600.dy, center900.dy, }, hasLength(4)); await _pumpHeartTime(tester, clock, const Duration(milliseconds: 700)); expect(completedCalls, 1); }); } Future _pumpHeartTime( WidgetTester tester, _HeartClock clock, Duration duration, ) async { const frameMs = 16; var remainingMs = duration.inMilliseconds; while (remainingMs > 0) { final stepMs = remainingMs >= frameMs ? frameMs : remainingMs; clock.advance(Duration(milliseconds: stepMs)); await tester.pump(const Duration(milliseconds: frameMs)); remainingMs -= stepMs; } } class _HeartClock { DateTime _now = DateTime(2026, 10, 26, 12); DateTime now() => _now; void advance(Duration duration) { _now = _now.add(duration); } }