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