import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:melo/shared/sub_tabs.dart'; import 'package:melo/shared/theme.dart'; Widget _wrap(Widget child) => MaterialApp( theme: MeloTheme.dark, home: Scaffold(body: child), ); void main() { testWidgets('jeder Reiter ist mindestens 48 dp hoch — sonst trifft man ihn ' 'im Gehen nicht', (tester) async { await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie', 'Künstler'], index: 0, onChanged: (_) {}, ))); await tester.pumpAndSettle(); for (final label in ['Lieder', 'Kategorie', 'Künstler']) { final flaeche = tester.getSize(find.ancestor( of: find.text(label), matching: find.byType(AnimatedContainer), )); expect(flaeche.height, greaterThanOrEqualTo(MeloTheme.minTouchTarget), reason: 'Reiter "$label" ist nur ${flaeche.height} dp hoch'); expect(flaeche.width, greaterThanOrEqualTo(MeloTheme.minTouchTarget)); } }); testWidgets('ein Tipp meldet den angetippten Reiter', (tester) async { int? gewaehlt; await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie'], index: 0, onChanged: (i) => gewaehlt = i, ))); await tester.tap(find.text('Kategorie')); await tester.pumpAndSettle(); expect(gewaehlt, 1); }); testWidgets('der aktive Reiter ist auch ohne Farbe erkennbar', (tester) async { final semantik = tester.ensureSemantics(); await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie'], index: 1, onChanged: (_) {}, ))); await tester.pumpAndSettle(); // Vorlesehilfen brauchen die Auswahl als Zustand, nicht als Farbe — // wer die weiße Pille nicht sieht, erfährt sonst nie, wo er ist. expect( tester.getSemantics(find.text('Kategorie')), isSemantics(label: 'Kategorie', isSelected: true, isButton: true), ); expect( tester.getSemantics(find.text('Lieder')), isSemantics(label: 'Lieder', isSelected: false, isButton: true), ); // Muss vor Testende passieren — addTearDown wäre zu spät. semantik.dispose(); }); testWidgets('jeder Reiter gibt beim Antippen Rückmeldung (Ripple)', (tester) async { await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie'], index: 0, onChanged: (_) {}, ))); // Ohne InkWell bliebe der Tipp unbestätigt — der Nutzer weiß nicht, // ob er getroffen hat. expect(find.byType(InkWell), findsNWidgets(2)); }); bool istPunkt(Widget w) => w is Container && (w.decoration as BoxDecoration?)?.shape == BoxShape.circle; testWidgets( 'ein Punkt zeigt an, dass im anderen Reiter etwas im Hintergrund läuft', (tester) async { await tester.pumpWidget(_wrap(SubTabs( labels: const ['YouTube', 'Server'], index: 0, badges: const [false, true], onChanged: (_) {}, ))); await tester.pumpAndSettle(); expect( find.descendant( of: find.ancestor( of: find.text('Server'), matching: find.byType(AnimatedContainer)), matching: find.byWidgetPredicate(istPunkt), ), findsOneWidget, ); expect( find.descendant( of: find.ancestor( of: find.text('YouTube'), matching: find.byType(AnimatedContainer)), matching: find.byWidgetPredicate(istPunkt), ), findsNothing, ); }); testWidgets('ohne badges-Angabe bleibt ein Reiter ohne Punkt', (tester) async { await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie'], index: 0, onChanged: (_) {}, ))); await tester.pumpAndSettle(); expect( find.descendant( of: find.ancestor( of: find.text('Lieder'), matching: find.byType(AnimatedContainer)), matching: find.byWidgetPredicate(istPunkt), ), findsNothing, ); }); testWidgets('der Wechsel wird animiert, nicht gesprungen', (tester) async { await tester.pumpWidget(_wrap(SubTabs( labels: const ['Lieder', 'Kategorie'], index: 0, onChanged: (_) {}, ))); await tester.pumpAndSettle(); final container = tester.widget(find.ancestor( of: find.text('Lieder'), matching: find.byType(AnimatedContainer), )); expect(container.duration, MeloMotion.fast); expect(container.curve, MeloMotion.curve); }); }