import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:pyramid/core/theme.dart'; import 'package:pyramid/layout/mobile_page_stack.dart'; import 'package:pyramid/widgets/spring_press.dart'; /// Handy-Übergang Chatliste ↔ Chat und die federnden Einblendungen. void main() { Widget host({required Widget? front, double motion = 1}) { return PyramidTheme( motion: motion, child: Directionality( textDirection: TextDirection.ltr, child: MobilePageStack( base: const _Counter(key: ValueKey('liste')), front: front, background: const Color(0xFF101010), ), ), ); } testWidgets('Chatliste behält ihren Zustand, Chat gleitet herein und hinaus', (tester) async { await tester.pumpWidget(host(front: null)); await tester.tap(find.byKey(const ValueKey('liste'))); await tester.pump(); expect(find.text('Zähler 1'), findsOneWidget); // Chat öffnen: gleitet von rechts herein. await tester.pumpWidget(host(front: const Text('Chat'))); await tester.pump(const Duration(milliseconds: 40)); final midX = tester.getTopLeft(find.text('Chat')).dx; expect(midX, greaterThan(0), reason: 'Chat sollte noch hereingleiten'); await tester.pumpAndSettle(); expect(tester.getTopLeft(find.text('Chat')).dx, 0); // Verdeckte Liste ist nicht mehr bedienbar, aber noch aufgebaut. expect(find.text('Zähler 1', skipOffstage: false), findsOneWidget); // Zurück: Chat gleitet hinaus und wird danach entfernt. await tester.pumpWidget(host(front: null)); await tester.pump(const Duration(milliseconds: 40)); expect(find.text('Chat'), findsOneWidget, reason: 'noch beim Hinausgleiten'); // Schon während des Hinausgleitens ist die Liste wieder bedienbar. await tester.tapAt(const Offset(20, 300)); await tester.pumpAndSettle(); expect(find.text('Zähler 2'), findsOneWidget); await tester.pumpAndSettle(); expect(find.text('Chat'), findsNothing); // Zustand der Liste ist erhalten geblieben. await tester.tap(find.byKey(const ValueKey('liste'))); await tester.pump(); expect(find.text('Zähler 3'), findsOneWidget); }); testWidgets('Animationen 0 %: sofortiger Wechsel ohne Gleiten', ( tester, ) async { await tester.pumpWidget(host(front: null, motion: 0)); await tester.pumpWidget(host(front: const Text('Chat'), motion: 0)); await tester.pump(); expect(tester.getTopLeft(find.text('Chat')).dx, 0); await tester.pumpWidget(host(front: null, motion: 0)); await tester.pump(); expect(find.text('Chat'), findsNothing); }); testWidgets('PopIn spielt nur, wenn verlangt, und endet unverändert', ( tester, ) async { Widget pop(bool play) => PyramidTheme( child: Directionality( textDirection: TextDirection.ltr, child: Center( child: PopIn(play: play, child: const Text('Neu')), ), ), ); await tester.pumpWidget(pop(true)); await tester.pump(const Duration(milliseconds: 30)); final opacity = tester.widget( find.ancestor(of: find.text('Neu'), matching: find.byType(Opacity)), ); expect(opacity.opacity, lessThan(1)); await tester.pumpAndSettle(); expect( tester .widget( find.ancestor(of: find.text('Neu'), matching: find.byType(Opacity)), ) .opacity, 1, ); await tester.pumpWidget(const SizedBox()); await tester.pumpWidget(pop(false)); expect( find.ancestor(of: find.text('Neu'), matching: find.byType(Opacity)), findsNothing, ); }); testWidgets('SpringBump hüpft nur beim Hochzählen', (tester) async { Widget bump(int v) => PyramidTheme( child: Directionality( textDirection: TextDirection.ltr, child: Center(child: SpringBump(value: v, child: Text('$v'))), ), ); double scale() => tester .widget( find.ancestor( of: find.byType(Text), matching: find.byType(Transform), ), ) .transform .storage[0]; // x-Skalierung (z bleibt bei Transform.scale immer 1) await tester.pumpWidget(bump(1)); expect(scale(), 1); await tester.pumpWidget(bump(2)); await tester.pump(const Duration(milliseconds: 16)); expect(scale(), lessThan(1), reason: 'Hochzählen soll hüpfen'); await tester.pumpAndSettle(); expect(scale(), closeTo(1, 0.001)); await tester.pumpWidget(bump(0)); await tester.pump(const Duration(milliseconds: 16)); expect(scale(), closeTo(1, 0.001), reason: 'Herunterzählen hüpft nicht'); }); } class _Counter extends StatefulWidget { const _Counter({super.key}); @override State<_Counter> createState() => _CounterState(); } class _CounterState extends State<_Counter> { int _n = 0; @override Widget build(BuildContext context) => GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => setState(() => _n++), child: Center(child: Text('Zähler $_n')), ); }