234 lines
7.7 KiB
Dart
234 lines
7.7 KiB
Dart
// Author: fruit.com.cn
|
|
// 职责:验证收藏点赞图标点击回弹、固定触控区与减少动画行为。
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:fruitshorts/core/theme/app_colors.dart';
|
|
import 'package:fruitshorts/pages/player/widgets/interaction_rail.dart';
|
|
import 'package:fruitshorts/pages/player/widgets/interaction_rail_burst.dart';
|
|
|
|
const _saveKey = Key('save');
|
|
const _likeKey = Key('like');
|
|
const _commentsKey = Key('comments');
|
|
const _pressFrame = Duration(milliseconds: 30);
|
|
const _popFrame = Duration(milliseconds: 100);
|
|
const _buttonSize = 48.0;
|
|
const _compactGap = 6.0;
|
|
const _epsilon = 0.01;
|
|
const _cancelledMaximum = 0.8;
|
|
const _popMinimum = 1.22;
|
|
const _burstKey = Key('rail-tap-burst');
|
|
const _directionFrame = Duration(milliseconds: 60);
|
|
const _directionFrames = 4;
|
|
|
|
Future<void> _pumpRail(
|
|
WidgetTester tester, {
|
|
required VoidCallback onTap,
|
|
bool selected = false,
|
|
bool reduceMotion = false,
|
|
}) => tester.pumpWidget(
|
|
MaterialApp(
|
|
home: MediaQuery(
|
|
data: MediaQueryData(disableAnimations: reduceMotion),
|
|
child: Scaffold(
|
|
body: Center(
|
|
child: InteractionRail(
|
|
itemGap: interactionRailCompactGap,
|
|
buttonSize: _buttonSize,
|
|
items: [
|
|
InteractionRailItem(
|
|
key: _saveKey,
|
|
label: 'Save',
|
|
icon: Icons.bookmark_outline,
|
|
selected: selected,
|
|
animateTap: true,
|
|
onTap: onTap,
|
|
),
|
|
InteractionRailItem(
|
|
key: _likeKey,
|
|
label: 'Like',
|
|
icon: Icons.favorite_outline,
|
|
selected: selected,
|
|
animateTap: true,
|
|
onTap: onTap,
|
|
),
|
|
InteractionRailItem(
|
|
key: _commentsKey,
|
|
label: 'Comments',
|
|
icon: Icons.chat_bubble_outline,
|
|
onTap: onTap,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Finder _button(Key key) =>
|
|
find.descendant(of: find.byKey(key), matching: find.byType(IconButton));
|
|
|
|
double _scale(WidgetTester tester, Key key) => tester
|
|
.widget<Transform>(
|
|
find
|
|
.descendant(of: find.byKey(key), matching: find.byType(Transform))
|
|
.first,
|
|
)
|
|
.transform
|
|
.entry(0, 0);
|
|
|
|
void _expectBurst(WidgetTester tester, Key key, bool cancelling) {
|
|
final burst = find.descendant(
|
|
of: find.byKey(key),
|
|
matching: find.byKey(_burstKey),
|
|
);
|
|
expect(burst, findsOneWidget);
|
|
final feedback = tester.widget<RailTapBurst>(burst);
|
|
expect(feedback.contracting, cancelling);
|
|
expect(feedback.color, cancelling ? AppColors.onPrimary : AppColors.brandRed);
|
|
final paint = find.descendant(of: burst, matching: find.byType(CustomPaint));
|
|
expect(tester.renderObject(paint), paints..line());
|
|
expect(tester.renderObject(paint), paintsExactlyCountTimes(#drawCircle, 0));
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('should_keep_compact_spacing_when_rail_is_idle', (tester) async {
|
|
await _pumpRail(tester, onTap: () {});
|
|
final save = tester.getRect(find.byKey(_saveKey));
|
|
final like = tester.getRect(find.byKey(_likeKey));
|
|
expect(like.top - save.bottom, closeTo(_compactGap, _epsilon));
|
|
});
|
|
|
|
for (final key in [_saveKey, _likeKey]) {
|
|
for (final selected in [false, true]) {
|
|
testWidgets(
|
|
'should_pop_icon_once_when_tapped_${key}_selected_$selected',
|
|
(tester) async {
|
|
var calls = 0;
|
|
await _pumpRail(tester, selected: selected, onTap: () => calls++);
|
|
final rect = tester.getRect(_button(key));
|
|
final itemRect = tester.getRect(find.byKey(key));
|
|
await tester.tap(_button(key));
|
|
await tester.pump();
|
|
await tester.pump(_pressFrame);
|
|
expect(_scale(tester, key), selected ? lessThan(1) : greaterThan(1));
|
|
await tester.pump(_popFrame);
|
|
expect(
|
|
_scale(tester, key),
|
|
selected ? lessThan(_cancelledMaximum) : greaterThan(_popMinimum),
|
|
);
|
|
_expectBurst(tester, key, selected);
|
|
expect(tester.getRect(_button(key)), rect);
|
|
expect(tester.getRect(find.byKey(key)), itemRect);
|
|
expect(rect.size, const Size.square(_buttonSize));
|
|
expect(calls, 1);
|
|
for (var frame = 0; frame < _directionFrames; frame++) {
|
|
await tester.pump(_directionFrame);
|
|
expect(
|
|
_scale(tester, key),
|
|
selected ? lessThanOrEqualTo(1) : greaterThanOrEqualTo(1),
|
|
);
|
|
}
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(_burstKey), findsNothing);
|
|
expect(_scale(tester, key), closeTo(1, _epsilon));
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
testWidgets(
|
|
'should_restart_feedback_without_duplicate_callbacks_when_rapid',
|
|
(tester) async {
|
|
var calls = 0;
|
|
await _pumpRail(tester, onTap: () => calls++);
|
|
for (final _ in [false, true]) {
|
|
await tester.tap(_button(_likeKey));
|
|
await tester.pump();
|
|
await tester.pump(_popFrame);
|
|
}
|
|
expect(calls, 2);
|
|
await tester.pumpAndSettle();
|
|
expect(_scale(tester, _likeKey), closeTo(1, _epsilon));
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
);
|
|
|
|
testWidgets('should_keep_callbacks_without_motion_when_reduced', (
|
|
tester,
|
|
) async {
|
|
var calls = 0;
|
|
await _pumpRail(tester, reduceMotion: true, onTap: () => calls++);
|
|
await tester.tap(_button(_saveKey));
|
|
await tester.pump(_pressFrame);
|
|
expect(_scale(tester, _saveKey), 1);
|
|
expect(find.byKey(_burstKey), findsNothing);
|
|
expect(calls, 1);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('should_keep_burst_intent_when_selection_updates_during_motion', (
|
|
tester,
|
|
) async {
|
|
var calls = 0;
|
|
void onTap() => calls++;
|
|
await _pumpRail(tester, onTap: onTap);
|
|
await tester.tap(_button(_likeKey));
|
|
await tester.pump();
|
|
await tester.pump(_popFrame);
|
|
await _pumpRail(tester, selected: true, onTap: onTap);
|
|
expect(
|
|
tester.widget<RailTapBurst>(find.byKey(_burstKey)).contracting,
|
|
isFalse,
|
|
);
|
|
await tester.tap(_button(_likeKey));
|
|
await tester.pump();
|
|
await tester.pump(_popFrame);
|
|
final feedback = tester.widget<RailTapBurst>(find.byKey(_burstKey));
|
|
expect(feedback.contracting, isTrue);
|
|
expect(feedback.color, AppColors.onPrimary);
|
|
expect(calls, 2);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(_burstKey), findsNothing);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('should_stop_burst_when_reduced_motion_changes_during_feedback', (
|
|
tester,
|
|
) async {
|
|
await _pumpRail(tester, onTap: () {});
|
|
await tester.tap(_button(_saveKey));
|
|
await tester.pump();
|
|
await tester.pump(_popFrame);
|
|
expect(find.byKey(_burstKey), findsOneWidget);
|
|
await _pumpRail(tester, reduceMotion: true, onTap: () {});
|
|
expect(find.byKey(_burstKey), findsNothing);
|
|
expect(_scale(tester, _saveKey), 1);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('should_not_pop_comments_when_motion_is_not_enabled', (
|
|
tester,
|
|
) async {
|
|
var calls = 0;
|
|
await _pumpRail(tester, onTap: () => calls++);
|
|
await tester.tap(_button(_commentsKey));
|
|
await tester.pump(_pressFrame);
|
|
expect(_scale(tester, _commentsKey), 1);
|
|
expect(find.byKey(_burstKey), findsNothing);
|
|
expect(calls, 1);
|
|
});
|
|
|
|
testWidgets('should_dispose_running_motion_when_rail_is_removed', (
|
|
tester,
|
|
) async {
|
|
await _pumpRail(tester, onTap: () {});
|
|
await tester.tap(_button(_likeKey));
|
|
await tester.pump(_pressFrame);
|
|
await tester.pumpWidget(const SizedBox.shrink());
|
|
await tester.pumpAndSettle();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
}
|