mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 12:52:13 +02:00
162 lines
5.8 KiB
Dart
162 lines
5.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter/semantics.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
|
import 'package:spotiflac_android/widgets/expressive_seek_track.dart';
|
|
import 'package:spotiflac_android/widgets/playback_seek_slider.dart';
|
|
|
|
Widget _host({
|
|
bool playing = true,
|
|
bool reduceMotion = false,
|
|
bool visible = true,
|
|
bool mornye = false,
|
|
TextDirection direction = TextDirection.ltr,
|
|
Duration position = const Duration(seconds: 30),
|
|
Duration duration = const Duration(seconds: 100),
|
|
Future<void> Function(Duration)? onSeek,
|
|
}) => MaterialApp(
|
|
theme: mornye ? MornyeTheme.build(Brightness.dark) : null,
|
|
home: MediaQuery(
|
|
data: MediaQueryData(disableAnimations: reduceMotion),
|
|
child: Directionality(
|
|
textDirection: direction,
|
|
child: TickerMode(
|
|
enabled: visible,
|
|
child: Scaffold(
|
|
body: Center(
|
|
child: PlaybackSeekSlider(
|
|
playing: playing,
|
|
position: position,
|
|
duration: duration,
|
|
onSeek: onSeek ?? (_) async {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
ExpressiveSeekTrackShape _track(WidgetTester tester) =>
|
|
SliderTheme.of(tester.element(find.byType(Slider))).trackShape!
|
|
as ExpressiveSeekTrackShape;
|
|
|
|
void main() {
|
|
testWidgets('wave travels during playback and settles smoothly on pause', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(_host());
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
final phase = _track(tester).phase;
|
|
expect(_track(tester).amplitude, 1);
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(_track(tester).phase, isNot(phase));
|
|
expect(tester.widget<Slider>(find.byType(Slider)).value, 30000);
|
|
|
|
await tester.pumpWidget(_host(playing: false));
|
|
await tester.pump(const Duration(milliseconds: 150));
|
|
expect(_track(tester).amplitude, greaterThan(0));
|
|
expect(_track(tester).amplitude, lessThan(1));
|
|
await tester.pumpAndSettle();
|
|
expect(_track(tester).amplitude, 0);
|
|
final pausedPhase = _track(tester).phase;
|
|
await tester.pump(const Duration(seconds: 2));
|
|
expect(_track(tester).phase, pausedPhase);
|
|
expect(tester.binding.hasScheduledFrame, isFalse);
|
|
|
|
await tester.pumpWidget(_host());
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
expect(_track(tester).amplitude, 1);
|
|
expect(_track(tester).phase, isNot(pausedPhase));
|
|
});
|
|
|
|
testWidgets('scrubbing flattens the track and playback restores the wave', (
|
|
tester,
|
|
) async {
|
|
final seeks = <Duration>[];
|
|
await tester.pumpWidget(_host(onSeek: (target) async => seeks.add(target)));
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
final bounds = tester.getRect(find.byType(Slider));
|
|
final gesture = await tester.startGesture(bounds.center);
|
|
await gesture.moveBy(Offset(bounds.width * 0.2, 0));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
expect(_track(tester).amplitude, 0);
|
|
expect(seeks, isEmpty);
|
|
final preview = tester.widget<Slider>(find.byType(Slider)).value;
|
|
await gesture.up();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
expect(seeks, [Duration(milliseconds: preview.round())]);
|
|
expect(_track(tester).amplitude, 1);
|
|
});
|
|
|
|
testWidgets('reduced motion and hidden routes stop the animation', (
|
|
tester,
|
|
) async {
|
|
for (final reduceMotion in [true, false]) {
|
|
await tester.pumpWidget(_host());
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
expect(_track(tester).amplitude, 1);
|
|
await tester.pumpWidget(
|
|
_host(reduceMotion: reduceMotion, visible: reduceMotion),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(_track(tester).amplitude, 0);
|
|
final phase = _track(tester).phase;
|
|
await tester.pump(const Duration(seconds: 2));
|
|
expect(_track(tester).phase, phase);
|
|
expect(tester.binding.hasScheduledFrame, isFalse);
|
|
}
|
|
});
|
|
|
|
testWidgets('unknown and completed tracks do not animate', (tester) async {
|
|
for (final duration in [Duration.zero, const Duration(seconds: 30)]) {
|
|
await tester.pumpWidget(_host(duration: duration));
|
|
await tester.pumpAndSettle();
|
|
expect(_track(tester).amplitude, 0);
|
|
expect(tester.binding.hasScheduledFrame, isFalse);
|
|
}
|
|
});
|
|
|
|
testWidgets('Mornye keeps its continuous track without a wave or handle', (
|
|
tester,
|
|
) async {
|
|
await tester.pumpWidget(_host(mornye: true));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(ExpressiveSeekTrack), findsNothing);
|
|
final theme = SliderTheme.of(tester.element(find.byType(Slider)));
|
|
expect(theme.trackShape, isA<RoundedRectSliderTrackShape>());
|
|
expect(theme.thumbShape, SliderComponentShape.noThumb);
|
|
expect(tester.binding.hasScheduledFrame, isFalse);
|
|
});
|
|
|
|
testWidgets('RTL seeking and screen reader adjustment remain available', (
|
|
tester,
|
|
) async {
|
|
final semantics = tester.ensureSemantics();
|
|
final seeks = <Duration>[];
|
|
await tester.pumpWidget(
|
|
_host(
|
|
direction: TextDirection.rtl,
|
|
onSeek: (target) async => seeks.add(target),
|
|
),
|
|
);
|
|
final bounds = tester.getRect(find.byType(Slider));
|
|
await tester.tapAt(
|
|
Offset(bounds.left + bounds.width * 0.2, bounds.center.dy),
|
|
);
|
|
await tester.pump();
|
|
expect(seeks.single.inSeconds, greaterThan(70));
|
|
final node = tester.semantics.simulatedAccessibilityTraversal().singleWhere(
|
|
(node) => node.getSemanticsData().flagsCollection.isSlider,
|
|
);
|
|
expect(node.getSemanticsData().hasAction(SemanticsAction.increase), isTrue);
|
|
node.owner!.performAction(node.id, SemanticsAction.increase);
|
|
await tester.pump();
|
|
expect(seeks, hasLength(2));
|
|
expect(seeks.last.inSeconds, greaterThan(30));
|
|
semantics.dispose();
|
|
});
|
|
}
|