// Author: fruit.com.cn import 'dart:math'; 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('should_show_randomized_heart_particles_when_burst_starts', ( tester, ) async { final random = _RecordingRandom(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: PlayerDoubleTapHeartBurst( burstId: 1, onCompleted: _noop, random: random, ), ), ), ); await tester.pump(const Duration(milliseconds: 500)); final hearts = find.byIcon(Icons.favorite_rounded); final count = hearts.evaluate().length; expect(count, inInclusiveRange(6, 8)); for (var index = 0; index < count; index++) { expect(_size(tester, index), inInclusiveRange(22, 34)); } expect(random.intBounds.first, 3); expect(random.intBounds.where((bound) => bound == 21), hasLength(7)); expect(random.intBounds.where((bound) => bound == 201), hasLength(8)); expect(random.intBounds.where((bound) => bound == 41), hasLength(8)); expect(random.intBounds.where((bound) => bound == 61), hasLength(8)); expect(random.intBounds.where((bound) => bound == 13), hasLength(8)); expect( tester .widgetList( find.descendant( of: find.byType(PlayerDoubleTapHeartBurst), matching: find.byType(Stack), ), ) .every((stack) => stack.clipBehavior == Clip.none), isTrue, ); }); test('heart particles fade from the start and scale in two phases', () { expect(PlayerDoubleTapHeartBurst.opacityForProgress(0.5), lessThan(0.7)); expect(PlayerDoubleTapHeartBurst.scaleForProgress(0), 0.55); expect( PlayerDoubleTapHeartBurst.scaleForProgress(0.3), closeTo(1.15, 1e-9), ); expect(PlayerDoubleTapHeartBurst.scaleForProgress(1), closeTo(0.2, 1e-9)); }); testWidgets('heart burst completes around 1.2 to 1.4 seconds', ( tester, ) async { var completed = false; var elapsedMs = 0; await tester.pumpWidget( MaterialApp( home: Scaffold( body: PlayerDoubleTapHeartBurst( burstId: 2, random: _RecordingRandom(), onCompleted: () => completed = true, ), ), ), ); while (!completed && elapsedMs < 2000) { await tester.pump(const Duration(milliseconds: 16)); elapsedMs += 16; } expect(completed, isTrue); expect(elapsedMs, greaterThanOrEqualTo(1200)); expect(elapsedMs, lessThanOrEqualTo(1400)); final count = find.byIcon(Icons.favorite_rounded).evaluate().length; expect(count, inInclusiveRange(6, 8)); }); } double _size(WidgetTester tester, int index) { return tester.widget(find.byKey(Key('double-tap-heart-$index'))).size!; } void _noop() {} class _RecordingRandom implements Random { final List intBounds = []; @override bool nextBool() => true; @override double nextDouble() => 0.5; @override int nextInt(int max) { intBounds.add(max); return max - 1; } }