Bug (Code-Review, P0-nah): dragEnd() wählte die Entscheidungslogik allein über das Vorzeichen von strecke. Bei bereits vollständig offenem Player klappte ein schwaches, versehentliches Fingerzittern nach oben (kleine negative Strecke, weit unter der Wisch-Schwelle) den Player ungewollt zu, weil das Vorzeichen in den ÖFFNEN-Zweig führte statt in den (korrekten) Schließen-Zweig, der bei so einer schwachen Geste keine Wirkung hätte. dragEnd() bekommt jetzt den Pflichtparameter `ausVollbild`, der den Aufrufer-Kontext explizit benennt statt ihn zu erraten. mini_player.dart (Öffnen-Geste) ruft mit ausVollbild: false, now_playing_screen.dart (Schließen-Geste) mit ausVollbild: true. Test zuerst (RED mit der alten 3-Parameter-Signatur reproduziert, dann GREEN nach dem Fix): test/player/player_expansion_controller_test.dart. Bestehende Tests (player_expansion_controller_test.dart, home_shell_expansion_test.dart) an die neue Signatur angepasst. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SM6e73V9ZethPiqHSocxUA
193 lines
6.4 KiB
Dart
193 lines
6.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:melo/player/player_expansion_controller.dart';
|
|
|
|
void main() {
|
|
Widget mitMediaQuery(bool disableAnimations, WidgetBuilder builder) {
|
|
return MediaQuery(
|
|
data: MediaQueryData(disableAnimations: disableAnimations),
|
|
child: MaterialApp(home: Builder(builder: builder)),
|
|
);
|
|
}
|
|
|
|
testWidgets('progress startet bei 0', (tester) async {
|
|
late PlayerExpansionController controller;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets('dragBy nach oben erhöht progress, nach unten verringert ihn',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.dragBy(-150); // halbe Referenzhöhe nach oben
|
|
expect(controller.progress, closeTo(0.5, 0.001));
|
|
|
|
controller.dragBy(75); // ein Viertel zurück nach unten
|
|
expect(controller.progress, closeTo(0.25, 0.001));
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets('dragBy clampt auf [0, 1]', (tester) async {
|
|
late PlayerExpansionController controller;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.dragBy(-1000);
|
|
expect(controller.progress, 1.0);
|
|
controller.dragBy(2000);
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets('open() fliegt zu 1, close() fliegt zu 0', (tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.open(ctx);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 1.0);
|
|
|
|
controller.close(ctx);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets('open()/close() springen bei disableAnimations sofort',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(true, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.open(ctx);
|
|
expect(controller.progress, 1.0); // ohne pumpAndSettle sofort da
|
|
controller.close(ctx);
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'dragEnd beim Öffnen: ausreichende Strecke nach oben fliegt zu 1',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.dragBy(-200);
|
|
controller.dragEnd(ctx, -200, 0, ausVollbild: false); // Strecke > wischStreckeSchwelle (80)
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 1.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'dragEnd beim Öffnen: zu kurze, langsame Geste fällt zurück auf 0',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.dragBy(-30);
|
|
controller.dragEnd(ctx, -30, 0, ausVollbild: false); // unter wischStreckeSchwelle
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'dragEnd beim Schließen: ausreichende Strecke nach unten fliegt zu 0',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.open(ctx);
|
|
await tester.pumpAndSettle();
|
|
controller.dragBy(200); // nach unten, Betrag > Referenzhöhe/2
|
|
controller.dragEnd(ctx, 200, 0, ausVollbild: true);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 0.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'dragEnd beim Schließen: zu kurze, langsame Geste bleibt bei 1',
|
|
(tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.open(ctx);
|
|
await tester.pumpAndSettle();
|
|
controller.dragBy(30);
|
|
controller.dragEnd(ctx, 30, 0, ausVollbild: true);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 1.0);
|
|
controller.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'dragEnd bei bereits vollem Player: schwaches Fingerzittern nach oben '
|
|
'klappt ihn NICHT zu (asymmetrischer Bug — dragEnd darf die '
|
|
'Entscheidungsrichtung nicht aus dem Vorzeichen von strecke raten, '
|
|
'sondern muss den Aufrufer-Kontext kennen)', (tester) async {
|
|
late PlayerExpansionController controller;
|
|
late BuildContext ctx;
|
|
await tester.pumpWidget(mitMediaQuery(false, (context) {
|
|
ctx = context;
|
|
controller = PlayerExpansionController(vsync: tester);
|
|
return const SizedBox();
|
|
}));
|
|
|
|
controller.open(ctx);
|
|
await tester.pumpAndSettle();
|
|
// Der Player ist voll offen (progress==1). Der Nutzer verrutscht beim
|
|
// Versuch, einen Knopf zu treffen, leicht nach oben — eine winzige,
|
|
// negative Strecke weit unter der Wisch-Schwelle. Das darf gar keine
|
|
// Zustandsänderung auslösen, weil die Geste viel zu schwach ist — schon
|
|
// gar nicht ein Zuklappen, das würde "nach oben ziehen schließt den
|
|
// offenen Player" bedeuten, was es nicht tut.
|
|
controller.dragBy(-5);
|
|
controller.dragEnd(ctx, -5, 0, ausVollbild: true);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.progress, 1.0);
|
|
controller.dispose();
|
|
});
|
|
}
|