// Author: fruit.com.cn // 职责:验证真实主题及素材在同步状态刷新和保留状态重组后的点击绘制。 import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:fruitshorts/core/theme/app_colors.dart'; import 'package:fruitshorts/core/theme/app_theme.dart'; import 'package:fruitshorts/pages/player/widgets/interaction_rail.dart'; import 'package:fruitshorts/pages/player/widgets/interaction_rail_burst.dart'; const _itemKey = Key('asset-like'); const _burstKey = Key('rail-tap-burst'); const _buttonSize = 48.0; const _iconSize = 36.0; const _motionFrame = Duration(milliseconds: 130); const _asset = 'assets/icon/rail/rail_like.png'; const _epsilon = 0.01; const _rayLimit = 26.4; const _rayLengthLimit = 3.5; const _burstPhases = [0.2, 0.5, 0.8]; Finder get _button => find.descendant( of: find.byKey(_itemKey), matching: find.byType(IconButton), ); Finder get _image => find.descendant(of: find.byKey(_itemKey), matching: find.byType(Image)); double _paintedWidth(WidgetTester tester) { final icon = tester.renderObject(_image); final rail = tester.renderObject(find.byKey(_itemKey)); return MatrixUtils.transformRect( icon.getTransformTo(rail), Offset.zero & icon.size, ).width; } Future _pumpAssetRail( WidgetTester tester, ValueNotifier selected, ) => tester.pumpWidget( MaterialApp( theme: AppTheme.dark(), home: Scaffold( body: Center( child: ValueListenableBuilder( valueListenable: selected, builder: (context, value, _) => InteractionRail( buttonSize: _buttonSize, iconSize: _iconSize, items: [ InteractionRailItem( key: _itemKey, label: 'Like', asset: _asset, selected: value, selectedColor: AppColors.brandRed, animateTap: true, onTap: () => selected.value = !selected.value, ), ], ), ), ), ), ), ); void main() { testWidgets('should_not_show_ink_or_shadow_when_button_is_pressed', ( tester, ) async { final selected = ValueNotifier(false); addTearDown(selected.dispose); await _pumpAssetRail(tester, selected); final button = tester.widget(_button); const pressed = {WidgetState.pressed}; expect(button.style?.overlayColor?.resolve(pressed), AppColors.transparent); expect(button.style?.shadowColor?.resolve(pressed), AppColors.transparent); expect(button.style?.splashFactory, NoSplash.splashFactory); expect(button.style?.elevation?.resolve(pressed), 0); }); for (final contracting in [false, true]) { testWidgets('should_keep_rays_compact_when_contracting_$contracting', ( tester, ) async { for (final progress in _burstPhases) { await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Center( child: SizedBox.square( dimension: _buttonSize, child: RailTapBurst( progress: progress, color: AppColors.brandRed, contracting: contracting, ), ), ), ), ); final paint = tester.renderObject(find.byType(CustomPaint)); expect(paint, paintsExactlyCountTimes(#drawCircle, 0)); expect(paint, paintsExactlyCountTimes(#drawLine, 8)); expect(paint, paints..everything(_isCompactRay)); } }); } testWidgets('should_paint_feedback_at_button_size_when_asset_is_tapped', ( tester, ) async { final selected = ValueNotifier(false); addTearDown(selected.dispose); await _pumpAssetRail(tester, selected); final touchRect = tester.getRect(_button); await tester.tap(_button); await tester.pump(); await tester.pump(_motionFrame); expect(selected.value, isTrue); expect(_paintedWidth(tester), greaterThan(_iconSize)); expect( tester.getSize(find.byKey(_burstKey)), const Size.square(_buttonSize), ); expect(tester.getRect(_button), touchRect); await tester.pumpAndSettle(); expect(_paintedWidth(tester), closeTo(_iconSize, _epsilon)); expect(tester.takeException(), isNull); }); testWidgets( 'should_paint_opposite_directions_after_preserved_state_reassembly', (tester) async { final selected = ValueNotifier(false); addTearDown(selected.dispose); await _pumpAssetRail(tester, selected); unawaited(tester.binding.reassembleApplication()); await tester.pump(); for (final cancelling in [false, true]) { await tester.tap(_button); await tester.pump(); await tester.pump(_motionFrame); expect( _paintedWidth(tester), cancelling ? lessThan(_iconSize) : greaterThan(_iconSize), ); final feedback = tester.widget(find.byKey(_burstKey)); expect(feedback.contracting, cancelling); expect(selected.value, !cancelling); await tester.pumpAndSettle(); expect(_paintedWidth(tester), closeTo(_iconSize, _epsilon)); } expect(tester.takeException(), isNull); }, ); } bool _isCompactRay(Symbol method, List arguments) { if (method != #drawLine) return true; const center = Offset(_buttonSize / 2, _buttonSize / 2); final start = arguments[0] as Offset; final end = arguments[1] as Offset; return (start - center).distance <= _rayLimit && (end - center).distance <= _rayLimit && (end - start).distance <= _rayLengthLimit; }