Files
Melo/test/shared/einblend_item_test.dart
Hermes (Server) e2304b66cd Gestaffelte Enter-Animation für Songlisten (EinblendItem)
Items in song_list.dart und artist_list.dart faden/rutschen beim ersten
Erscheinen gestaffelt statt abrupt ein — angelehnt an HyperOS' fließende
Motion-Sprache. Läuft einmal pro Element, respektiert Reduce-Motion und
deckelt die Verzögerung ab Index 12.
2026-08-29 12:51:04 +02:00

77 lines
2.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:melo/shared/einblend_item.dart';
void main() {
Widget wrap(Widget child, {bool ruhig = false}) => MaterialApp(
home: MediaQuery(
data: MediaQueryData(disableAnimations: ruhig),
child: Scaffold(body: child),
),
);
double opacityVon(WidgetTester tester, Finder einblendItem) => tester
.widget<Opacity>(
find.descendant(of: einblendItem, matching: find.byType(Opacity)))
.opacity;
double versatzYVon(WidgetTester tester, Finder einblendItem) => tester
.widget<Transform>(
find.descendant(of: einblendItem, matching: find.byType(Transform)))
.transform
.getTranslation()
.y;
testWidgets('Opacity startet bei 0 und erreicht 1,0 nach dem Einblenden',
(tester) async {
await tester.pumpWidget(wrap(
const EinblendItem(index: 0, child: Text('Titel')),
));
final item = find.byType(EinblendItem);
expect(opacityVon(tester, item), 0);
await tester.pumpAndSettle();
expect(opacityVon(tester, item), 1);
expect(versatzYVon(tester, item), 0);
});
testWidgets(
'bei abgeschalteten Systemanimationen erscheint das Element sofort',
(tester) async {
await tester.pumpWidget(wrap(
const EinblendItem(index: 3, child: Text('Titel')),
ruhig: true,
));
final item = find.byType(EinblendItem);
// Bewusst kein weiterer pump() davor: die Reduce-Motion-Einstellung
// darf nicht erst nach einer Zeitverzögerung greifen.
expect(opacityVon(tester, item), 1);
expect(versatzYVon(tester, item), 0);
});
testWidgets('der Deckel bremst die Verzögerung ab Index 12',
(tester) async {
await tester.pumpWidget(wrap(
const Column(
children: [
EinblendItem(index: 12, child: Text('Zwölf')),
EinblendItem(index: 50, child: Text('Fünfzig')),
],
),
));
// Deckel: min(index, 12) * 25ms → beide starten spätestens nach 300ms.
// Ohne Deckel bräuchte Index 50 1250ms Verzögerung und wäre hier noch
// bei Opacity 0.
await tester.pump(const Duration(milliseconds: 300));
await tester.pump(const Duration(milliseconds: 220));
for (final item in find.byType(EinblendItem).evaluate()) {
expect(opacityVon(tester, find.byWidget(item.widget)), 1);
}
});
}