Files
Melo/test/player/player_expansion_controller_test.dart
Hermes (Server)andClaude Sonnet 5 6687f2f67c Fix: PlayerExpansionController.dragEnd rät Öffnen/Schließen nicht mehr aus Vorzeichen
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
2026-08-29 15:36:46 +02:00

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();
});
}