import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hepan_everyday/widgets/directional_week_transition.dart'; Widget _week(int week, {bool reduceMotion = false, VoidCallback? onTap}) => MaterialApp( home: MediaQuery( data: MediaQueryData(disableAnimations: reduceMotion), child: Scaffold( body: DirectionalWeekTransition( week: week, child: SizedBox( width: 320, height: 180, child: Center( child: Semantics( label: 'week-$week-course', button: true, excludeSemantics: true, child: TextButton( onPressed: onTap ?? () {}, child: Text('Week $week'), ), ), ), ), ), ), ), ); FractionalTranslation _translation(WidgetTester tester, String layer) => tester.widget(find.byKey(Key('week-transition-$layer'))); Finder get _layers => find.descendant( of: find.byType(DirectionalWeekTransition), matching: find.byType(FractionalTranslation), ); void main() { testWidgets( 'next and previous weeks move in opposite directions then settle', (tester) async { await tester.pumpWidget(_week(1)); await tester.pumpWidget(_week(2)); await tester.pump(const Duration(milliseconds: 100)); expect(find.text('Week 1'), findsOneWidget); expect(find.text('Week 2'), findsOneWidget); expect(_translation(tester, 'incoming').translation.dx, greaterThan(0)); expect(_translation(tester, 'outgoing').translation.dx, lessThan(0)); final fade = tester.widget( find.descendant( of: find.byKey(const Key('week-transition-incoming')), matching: find.byType(Opacity), ), ); expect(fade.opacity, greaterThan(0)); expect(fade.opacity, lessThan(1)); await tester.pumpAndSettle(); expect(find.text('Week 1'), findsNothing); expect(_translation(tester, 'incoming').translation, Offset.zero); await tester.pumpWidget(_week(1)); await tester.pump(const Duration(milliseconds: 100)); expect(_translation(tester, 'incoming').translation.dx, lessThan(0)); expect(_translation(tester, 'outgoing').translation.dx, greaterThan(0)); await tester.pumpAndSettle(); expect(find.text('Week 2'), findsNothing); expect(_translation(tester, 'incoming').translation, Offset.zero); }, ); testWidgets('outgoing courses cannot receive taps or duplicate semantics', ( tester, ) async { final semantics = tester.ensureSemantics(); try { var oldTaps = 0; await tester.pumpWidget(_week(1, onTap: () => oldTaps++)); await tester.pumpWidget(_week(2)); await tester.pump(const Duration(milliseconds: 100)); expect(find.bySemanticsLabel('week-1-course'), findsNothing); expect(find.bySemanticsLabel('week-2-course'), findsOneWidget); final oldPointer = tester.widget( find .ancestor( of: find.text('Week 1'), matching: find.byType(IgnorePointer), ) .first, ); expect(oldPointer.ignoring, isTrue); await tester.tap(find.text('Week 1'), warnIfMissed: false); expect(oldTaps, 0); await tester.pumpAndSettle(); } finally { semantics.dispose(); } }); testWidgets( 'rapid requests keep the running tween and coalesce latest week', (tester) async { await tester.pumpWidget(_week(1)); await tester.pumpWidget(_week(2)); await tester.pump(const Duration(milliseconds: 60)); final before = _translation(tester, 'incoming').translation; await tester.pumpWidget(_week(3)); expect(_translation(tester, 'incoming').translation, before); await tester.pumpWidget(_week(4)); expect(_translation(tester, 'incoming').translation, before); expect(_layers, findsNWidgets(2)); expect(find.text('Week 3'), findsNothing); await tester.pumpAndSettle(); expect(find.text('Week 4'), findsOneWidget); expect(find.text('Week 1'), findsNothing); expect(find.text('Week 2'), findsNothing); expect(_layers, findsOneWidget); expect(_translation(tester, 'incoming').translation, Offset.zero); expect(tester.takeException(), isNull); }, ); testWidgets('rapid reversal returns to latest week without stale layers', ( tester, ) async { await tester.pumpWidget(_week(1)); await tester.pumpWidget(_week(2)); await tester.pump(const Duration(milliseconds: 70)); await tester.pumpWidget(_week(3)); await tester.pumpWidget(_week(1)); await tester.pump(const Duration(milliseconds: 210)); await tester.pump(const Duration(milliseconds: 100)); expect(_translation(tester, 'incoming').translation.dx, lessThan(0)); await tester.pumpAndSettle(); expect(find.text('Week 1'), findsOneWidget); expect(find.text('Week 2'), findsNothing); expect(find.text('Week 3'), findsNothing); expect(_layers, findsOneWidget); }); testWidgets('reduced motion snaps and clears an in-flight transition', ( tester, ) async { await tester.pumpWidget(_week(1)); await tester.pumpWidget(_week(2)); await tester.pump(const Duration(milliseconds: 70)); await tester.pumpWidget(_week(3, reduceMotion: true)); expect(find.text('Week 1'), findsNothing); expect(find.text('Week 2'), findsNothing); expect(find.text('Week 3'), findsOneWidget); expect(_translation(tester, 'incoming').translation, Offset.zero); expect(find.byKey(const Key('week-transition-outgoing')), findsNothing); await tester.pumpWidget(_week(1, reduceMotion: true)); expect(find.text('Week 1'), findsOneWidget); expect(find.text('Week 3'), findsNothing); expect(tester.hasRunningAnimations, isFalse); }); }