From de230d393cf2ec53372d5dcc9c2d45ec5ccd92c8 Mon Sep 17 00:00:00 2001 From: Xilai Zhang Date: Tue, 24 May 2022 11:10:13 -0700 Subject: [PATCH] Revert "Add ability for `ModalRoutes` to ignore pointers during transitions and do so on `Cupertino` routes (#95757)" (#104520) This reverts commit 4c0b0be2da5d1ee80c3d713e68ddd88d2cf2e72d. --- packages/flutter/lib/src/cupertino/route.dart | 9 - packages/flutter/lib/src/widgets/routes.dart | 207 ++++++---------- .../test/cupertino/action_sheet_test.dart | 29 +-- .../flutter/test/cupertino/dialog_test.dart | 2 - .../flutter/test/cupertino/route_test.dart | 221 ------------------ packages/flutter/test/material/page_test.dart | 5 +- .../flutter/test/widgets/routes_test.dart | 126 ---------- .../flutter_test/test/widget_tester_test.dart | 2 +- 8 files changed, 93 insertions(+), 508 deletions(-) diff --git a/packages/flutter/lib/src/cupertino/route.dart b/packages/flutter/lib/src/cupertino/route.dart index 0d268c247121..1968f402f772 100644 --- a/packages/flutter/lib/src/cupertino/route.dart +++ b/packages/flutter/lib/src/cupertino/route.dart @@ -235,9 +235,6 @@ mixin CupertinoRouteTransitionMixin on PageRoute { return result; } - @override - bool get ignorePointerDuringTransitions => true; - // Called by _CupertinoBackGestureDetector when a pop ("back") drag start // gesture is detected. The returned controller handles all of the subsequent // drag events. @@ -1052,9 +1049,6 @@ class CupertinoModalPopupRoute extends PopupRoute { @override Duration get transitionDuration => _kModalPopupTransitionDuration; - @override - bool get ignorePointerDuringTransitions => true; - Animation? _animation; late Tween _offsetTween; @@ -1355,7 +1349,4 @@ class CupertinoDialogRoute extends RawDialogRoute { barrierLabel: barrierLabel ?? CupertinoLocalizations.of(context).modalBarrierDismissLabel, barrierColor: barrierColor ?? CupertinoDynamicColor.resolve(kCupertinoModalBarrierColor, context), ); - - @override - bool get ignorePointerDuringTransitions => true; } diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 4ebce88c7333..83bcdcee9ad9 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -293,85 +293,73 @@ abstract class TransitionRoute extends OverlayRoute { final VoidCallback? previousTrainHoppingListenerRemover = _trainHoppingListenerRemover; _trainHoppingListenerRemover = null; - if (nextRoute is TransitionRoute) { - if (canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) { - final Animation? current = _secondaryAnimation.parent; - if (current != null) { - final Animation currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!; - final Animation nextTrain = nextRoute._animation!; - if ( - currentTrain.value == nextTrain.value || - nextTrain.status == AnimationStatus.completed || - nextTrain.status == AnimationStatus.dismissed - ) { - _setSecondaryAnimation(nextTrain, nextRoute.completed); - } else { - // Two trains animate at different values. We have to do train hopping. - // There are three possibilities of train hopping: - // 1. We hop on the nextTrain when two trains meet in the middle using - // TrainHoppingAnimation. - // 2. There is no chance to hop on nextTrain because two trains never - // cross each other. We have to directly set the animation to - // nextTrain once the nextTrain stops animating. - // 3. A new _updateSecondaryAnimation is called before train hopping - // finishes. We leave a listener remover for the next call to - // properly clean up the existing train hopping. - TrainHoppingAnimation? newAnimation; - void jumpOnAnimationEnd(AnimationStatus status) { - switch (status) { - case AnimationStatus.completed: - case AnimationStatus.dismissed: - // The nextTrain has stopped animating without train hopping. - // Directly sets the secondary animation and disposes the - // TrainHoppingAnimation. - _setSecondaryAnimation(nextTrain, nextRoute.completed); - if (_trainHoppingListenerRemover != null) { - _trainHoppingListenerRemover!(); - _trainHoppingListenerRemover = null; - } - break; - case AnimationStatus.forward: - case AnimationStatus.reverse: - break; - } - } - _trainHoppingListenerRemover = () { - nextTrain.removeStatusListener(jumpOnAnimationEnd); - newAnimation?.dispose(); - }; - nextTrain.addStatusListener(jumpOnAnimationEnd); - newAnimation = TrainHoppingAnimation( - currentTrain, - nextTrain, - onSwitchedTrain: () { - assert(_secondaryAnimation.parent == newAnimation); - assert(newAnimation!.currentTrain == nextRoute._animation); - // We can hop on the nextTrain, so we don't need to listen to - // whether the nextTrain has stopped. - _setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed); + if (nextRoute is TransitionRoute && canTransitionTo(nextRoute) && nextRoute.canTransitionFrom(this)) { + final Animation? current = _secondaryAnimation.parent; + if (current != null) { + final Animation currentTrain = (current is TrainHoppingAnimation ? current.currentTrain : current)!; + final Animation nextTrain = nextRoute._animation!; + if ( + currentTrain.value == nextTrain.value || + nextTrain.status == AnimationStatus.completed || + nextTrain.status == AnimationStatus.dismissed + ) { + _setSecondaryAnimation(nextTrain, nextRoute.completed); + } else { + // Two trains animate at different values. We have to do train hopping. + // There are three possibilities of train hopping: + // 1. We hop on the nextTrain when two trains meet in the middle using + // TrainHoppingAnimation. + // 2. There is no chance to hop on nextTrain because two trains never + // cross each other. We have to directly set the animation to + // nextTrain once the nextTrain stops animating. + // 3. A new _updateSecondaryAnimation is called before train hopping + // finishes. We leave a listener remover for the next call to + // properly clean up the existing train hopping. + TrainHoppingAnimation? newAnimation; + void jumpOnAnimationEnd(AnimationStatus status) { + switch (status) { + case AnimationStatus.completed: + case AnimationStatus.dismissed: + // The nextTrain has stopped animating without train hopping. + // Directly sets the secondary animation and disposes the + // TrainHoppingAnimation. + _setSecondaryAnimation(nextTrain, nextRoute.completed); if (_trainHoppingListenerRemover != null) { _trainHoppingListenerRemover!(); _trainHoppingListenerRemover = null; } - }, - ); - _setSecondaryAnimation(newAnimation, nextRoute.completed); + break; + case AnimationStatus.forward: + case AnimationStatus.reverse: + break; + } } - } else { // This route has no secondary animation. - _setSecondaryAnimation(nextRoute._animation, nextRoute.completed); + _trainHoppingListenerRemover = () { + nextTrain.removeStatusListener(jumpOnAnimationEnd); + newAnimation?.dispose(); + }; + nextTrain.addStatusListener(jumpOnAnimationEnd); + newAnimation = TrainHoppingAnimation( + currentTrain, + nextTrain, + onSwitchedTrain: () { + assert(_secondaryAnimation.parent == newAnimation); + assert(newAnimation!.currentTrain == nextRoute._animation); + // We can hop on the nextTrain, so we don't need to listen to + // whether the nextTrain has stopped. + _setSecondaryAnimation(newAnimation!.currentTrain, nextRoute.completed); + if (_trainHoppingListenerRemover != null) { + _trainHoppingListenerRemover!(); + _trainHoppingListenerRemover = null; + } + }, + ); + _setSecondaryAnimation(newAnimation, nextRoute.completed); } } else { - // This route cannot coordinate transitions with nextRoute, so it should - // have no visible secondary animation. By using an AnimationMin, the - // animation's value will always be zero, but it will have nextRoute.animation's - // status until it finishes, allowing this route to wait until all visible - // transitions are complete to stop ignoring pointers. - _setSecondaryAnimation( - AnimationMin(kAlwaysDismissedAnimation, nextRoute._animation!), - nextRoute.completed, - ); + _setSecondaryAnimation(nextRoute._animation, nextRoute.completed); } - } else { // The next route is not a TransitionRoute. + } else { _setSecondaryAnimation(kAlwaysDismissedAnimation); } // Finally, we dispose any previous train hopping animation because it @@ -408,9 +396,9 @@ abstract class TransitionRoute extends OverlayRoute { /// the [nextRoute] is popped off of this route, the /// `secondaryAnimation` will run from 1.0 - 0.0. /// - /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` - /// will proxy an animation with a constant value of 0. In other words, this - /// route will not animate when [nextRoute] is pushed on top of it or when + /// If false, this route's [ModalRoute.buildTransitions] `secondaryAnimation` parameter + /// value will be [kAlwaysDismissedAnimation]. In other words, this route + /// will not animate when [nextRoute] is pushed on top of it or when /// [nextRoute] is popped off of it. /// /// Returns true by default. @@ -858,19 +846,17 @@ class _ModalScopeState extends State<_ModalScope> { context, widget.route.animation!, widget.route.secondaryAnimation!, - // _listenable updates when this route's animations change - // values, but the _ignorePointerNotifier can also update - // when the status of animations on popping routes change, - // even when this route's animations' values don't. Also, - // when the value of the _ignorePointerNotifier changes, - // it's only necessary to rebuild the IgnorePointer - // widget and set the focus node's ability to focus. + // This additional AnimatedBuilder is include because if the + // value of the userGestureInProgressNotifier changes, it's + // only necessary to rebuild the IgnorePointer widget and set + // the focus node's ability to focus. AnimatedBuilder( - animation: widget.route._ignorePointerNotifier, + animation: widget.route.navigator?.userGestureInProgressNotifier ?? ValueNotifier(false), builder: (BuildContext context, Widget? child) { - focusScopeNode.canRequestFocus = !_shouldIgnoreFocusRequest; + final bool ignoreEvents = _shouldIgnoreFocusRequest; + focusScopeNode.canRequestFocus = !ignoreEvents; return IgnorePointer( - ignoring: widget.route._ignorePointer, + ignoring: ignoreEvents, child: child, ); }, @@ -1154,36 +1140,11 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute false; - @override void install() { super.install(); - _animationProxy = ProxyAnimation(super.animation) - ..addStatusListener(_handleAnimationStatusChanged); - _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation) - ..addStatusListener(_handleAnimationStatusChanged); - navigator!.userGestureInProgressNotifier.addListener(_maybeUpdateIgnorePointer); - } - - void _handleAnimationStatusChanged(AnimationStatus status) { - _maybeUpdateIgnorePointer(); + _animationProxy = ProxyAnimation(super.animation); + _secondaryAnimationProxy = ProxyAnimation(super.secondaryAnimation); } @override @@ -1419,19 +1380,6 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute? get secondaryAnimation => _secondaryAnimationProxy; ProxyAnimation? _secondaryAnimationProxy; - bool get _ignorePointer => _ignorePointerNotifier.value; - final ValueNotifier _ignorePointerNotifier = ValueNotifier(false); - - void _maybeUpdateIgnorePointer() { - bool isTransitioning(Animation? animation) { - return animation?.status == AnimationStatus.forward || animation?.status == AnimationStatus.reverse; - } - _ignorePointerNotifier.value = !isCurrent || - (navigator?.userGestureInProgress ?? false) || - (ignorePointerDuringTransitions && - (isTransitioning(animation) || isTransitioning(secondaryAnimation))); - } - final List _willPopCallbacks = []; /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with @@ -1650,14 +1598,9 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute navigatorKey = GlobalKey(); - bool homeTapped = false; - await tester.pumpWidget( - CupertinoApp( - navigatorKey: navigatorKey, - home: TextButton( - onPressed: () => homeTapped = true, - child: const Text('Home'), - ), - ), - ); - - navigatorKey.currentState!.push( - CupertinoPageRoute( - builder: (_) => const Text('Page 2'), - ) - ); - await tester.pumpAndSettle(); - - expect(find.text('Page 2'), findsOneWidget); - - navigatorKey.currentState!.pop(); - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.text('Page 2'), findsOneWidget); // Transition still in progress - - await tester.tap(find.text('Home'), warnIfMissed: false); // Home route is not tappable - expect(homeTapped, false); - - await tester.pumpAndSettle(); // Transition completes - - await tester.tap(find.text('Home')); - expect(homeTapped, true); - }); - - testWidgets('fullscreenDialog CupertinoPageRoute ignores pointers when route on top of it pops', (WidgetTester tester) async { - bool homeTapped = false; - await tester.pumpWidget( - CupertinoApp( - home: TextButton( - onPressed: () => homeTapped = true, - child: const Text('Home'), - ), - ), - ); - - tester.state(find.byType(Navigator)).push( - CupertinoPageRoute( - fullscreenDialog: true, - builder: (_) => const Text('Page 2'), - ) - ); - await tester.pumpAndSettle(); - - expect(find.text('Page 2'), findsOneWidget); - - tester.state(find.byType(Navigator)).pop(); - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.text('Page 2'), findsOneWidget); // Transition still in progress - - await tester.tap(find.text('Home'), warnIfMissed: false); // Home route is not tappable - expect(homeTapped, false); - - await tester.pumpAndSettle(); // Transition completes - - await tester.tap(find.text('Home')); - expect(homeTapped, true); - }); - - testWidgets('CupertinoPageRoute ignores pointers when user pop gesture is in progress', (WidgetTester tester) async { - bool homeTapped = false; - await tester.pumpWidget( - CupertinoApp( - home: TextButton( - onPressed: () => homeTapped = true, - child: const Text('Page 1'), - ), - ), - ); - - tester.state(find.byType(Navigator)).push( - CupertinoPageRoute( - builder: (_) => const Text('Page 2'), - ), - ); - await tester.pumpAndSettle(); - - expect(find.text('Page 1'), findsNothing); - - final TestGesture swipeGesture = await tester.startGesture(const Offset(5, 100)); - - await swipeGesture.moveBy(const Offset(100, 0)); - await tester.pump(); - - expect(find.text('Page 1'), findsOneWidget); - expect(tester.state(find.byType(Navigator)).userGestureInProgress, true); - - await tester.tap(find.text('Page 1'), warnIfMissed: false); - expect(homeTapped, false); - }); - - testWidgets('CupertinoPageRoute ignores pointers when it is pushed on top of other route', (WidgetTester tester) async { - await tester.pumpWidget( - CupertinoApp( - onGenerateRoute: (_) => CupertinoPageRoute( - builder: (_) => const Text('Home'), - ), - ), - ); - - await tester.tap(find.text('Home')); - tester.state(find.byType(Navigator)).push( - CupertinoPageRoute( - builder: (_) => const Text('Page 2'), - ), - ); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.text('Home'), findsOneWidget); // Transition still in progress - - // Can't test directly for taps because route is interactive but offstage - // One ignore pointer for each of two overlay entries (ModalScope, ModalBarrier) on each of two routes - expect(find.byType(IgnorePointer, skipOffstage: false), findsNWidgets(4)); - final List ignorePointers = find.byType(IgnorePointer, skipOffstage: false).evaluate().toList(); - expect((ignorePointers.first.widget as IgnorePointer).ignoring, true); // Home modalBarrier - expect((ignorePointers[1].widget as IgnorePointer).ignoring, true); // Home modalScope - expect((ignorePointers[2].widget as IgnorePointer).ignoring, true); // Page 2 modalBarrier - expect((ignorePointers.last.widget as IgnorePointer).ignoring, true); // Page 2 modalScope - }); - - testWidgets('showCupertinoDialog ignores pointers until transition completes', (WidgetTester tester) async { - await tester.pumpWidget( - CupertinoApp( - home: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () { - showCupertinoModalPopup( - context: context, - builder: (BuildContext innerContext) => TextButton( - onPressed: Navigator.of(innerContext).pop, - child: const Text('dialog'), - ), - ); - }, - child: const Text('Show Dialog'), - ); - }, - ), - ), - ); - - // Open the dialog. - await tester.tap(find.byType(TextButton)); - await tester.pump(const Duration(milliseconds: 100)); - - // Trigger pop while the transition is in progress - await tester.tap(find.text('dialog'), warnIfMissed: false); - await tester.pumpAndSettle(); - - // Transition is over and the dialog has not been dismissed - expect(find.text('dialog'), findsOneWidget); - - await tester.tap(find.text('dialog')); - await tester.pumpAndSettle(); - - // The dialog has not been dismissed - expect(find.text('dialog'), findsNothing); - }); - - testWidgets('showCupertinoModalPopup ignores pointers until transition completes', (WidgetTester tester) async { - await tester.pumpWidget( - CupertinoApp( - home: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () { - showCupertinoModalPopup( - context: context, - builder: (BuildContext innerContext) => TextButton( - onPressed: Navigator.of(innerContext).pop, - child: const Text('modal'), - ), - ); - }, - child: const Text('Show modal'), - ); - }, - ), - ), - ); - - // Open the modal popup - await tester.tap(find.byType(TextButton)); - await tester.pump(const Duration(milliseconds: 100)); - - // Trigger pop while the transition is in progress - await tester.tap(find.text('modal'), warnIfMissed: false); - await tester.pumpAndSettle(); - - // Transition is over and the dialog has not been dismissed - expect(find.text('modal'), findsOneWidget); - - await tester.tap(find.text('modal')); - await tester.pumpAndSettle(); - - // The dialog has not been dismissed - expect(find.text('modal'), findsNothing); - }); - }); - testWidgets('Animated push/pop is not linear', (WidgetTester tester) async { await tester.pumpWidget( const CupertinoApp( diff --git a/packages/flutter/test/material/page_test.dart b/packages/flutter/test/material/page_test.dart index 6082cd654358..42b63de600b8 100644 --- a/packages/flutter/test/material/page_test.dart +++ b/packages/flutter/test/material/page_test.dart @@ -432,7 +432,6 @@ void main() { routes: routes, ), ); - expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2); await tester.tap(find.text('PUSH')); expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2); expect(find.text('PUSH'), findsNothing); @@ -791,8 +790,8 @@ void main() { // Tapping the "page" route's back button doesn't do anything either. await tester.tap(find.byTooltip('Back'), warnIfMissed: false); - await tester.pump(); - expect(tester.getTopLeft(find.byKey(pageScaffoldKey, skipOffstage: false)), const Offset(400, 0)); + await tester.pumpAndSettle(); + expect(tester.getTopLeft(find.byKey(pageScaffoldKey)), const Offset(400, 0)); expect(tester.getTopLeft(find.byKey(homeScaffoldKey)).dx, lessThan(0)); }, variant: const TargetPlatformVariant({ TargetPlatform.iOS, TargetPlatform.macOS })); diff --git a/packages/flutter/test/widgets/routes_test.dart b/packages/flutter/test/widgets/routes_test.dart index 1120615ff7ef..ad36da4356dc 100644 --- a/packages/flutter/test/widgets/routes_test.dart +++ b/packages/flutter/test/widgets/routes_test.dart @@ -940,36 +940,6 @@ void main() { expect(trainHopper2.currentTrain, isNull); // Has been disposed. }); - testWidgets('secondary animation is AnimationMin when transition route that cannot be transitioned to or from pops', (WidgetTester tester) async { - final PageRoute pageRouteOne = MaterialPageRoute( - builder: (_) => const Text('Page One'), - ); - - await tester.pumpWidget( - MaterialApp( - onGenerateRoute: (_) => pageRouteOne, - ), - ); - - final PageRoute pageRouteTwo = MaterialPageRoute( - fullscreenDialog: true, - builder: (_) => const Text('Page Two'), - ); - - tester.state(find.byType(Navigator)).push(pageRouteTwo); - await tester.pumpAndSettle(); - - tester.state(find.byType(Navigator)).pop(); - await tester.pump(); - - expect( - ((pageRouteOne.secondaryAnimation! as ProxyAnimation).parent! as ProxyAnimation).parent, - isA>() - ..having((AnimationMin a) => a.first, 'first', equals(kAlwaysDismissedAnimation)) - ..having((AnimationMin a) => a.next, 'first', equals(pageRouteTwo.animation)), - ); - }); - testWidgets('secondary animation is triggered when pop initial route', (WidgetTester tester) async { final GlobalKey navigator = GlobalKey(); late Animation secondaryAnimationOfRouteOne; @@ -1041,41 +1011,6 @@ void main() { expect(find.byType(ModalBarrier), findsNWidgets(1)); }); - testWidgets('showGeneralDialog ModalBarrier does not ignore pointers during transitions', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - home: Builder( - builder: (BuildContext context) { - return TextButton( - onPressed: () { - showGeneralDialog( - context: context, - transitionDuration: const Duration(milliseconds: 400), - pageBuilder: (BuildContext innerContext, __, ___) => TextButton( - onPressed: Navigator.of(innerContext).pop, - child: const Text('dialog'), - ), - ); - }, - child: const Text('Show Dialog'), - ); - }, - ), - ), - ); - - // Open the dialog. - await tester.tap(find.byType(TextButton)); - await tester.pump(const Duration(milliseconds: 200)); - - // Trigger pop while the transition is in progress - await tester.tap(find.text('dialog')); - await tester.pumpAndSettle(); - - // The dialog has been dismissed mid-transition - expect(find.text('dialog'), findsNothing); - }); - testWidgets('showGeneralDialog adds non-dismissible barrier when barrierDismissible is false', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Builder( @@ -1371,67 +1306,6 @@ void main() { }); }); - testWidgets('does not ignore pointers when route on top of it pops', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - theme: ThemeData( - pageTransitionsTheme: const PageTransitionsTheme( - builders: { - // Use a transitions builder that will keep the underlying content - // partially visible during a transition - TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(), - }, - ) - ), - home: const Text('Home'), - ), - ); - - tester.state(find.byType(Navigator)).push( - MaterialPageRoute(builder: (_) => const Text('Page 2')) - ); - - await tester.pumpAndSettle(); - expect(find.text('Page 2'), findsOneWidget); - - tester.state(find.byType(Navigator)).pop(); - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.text('Page 2'), findsOneWidget); // Transition still in progress - - await tester.tap(find.text('Home')); // Home route is tappable - }); - - testWidgets('does not ignore pointers during its own entrance animation', (WidgetTester tester) async { - await tester.pumpWidget( - MaterialApp( - onGenerateRoute: (_) => MaterialPageRoute( - builder: (_) => const Text('Home'), - ), - ), - ); - - await tester.tap(find.text('Home')); - tester.state(find.byType(Navigator)).push( - MaterialPageRoute( - builder: (_) => const Text('Page 2'), - ), - ); - await tester.pump(); - await tester.pump(const Duration(milliseconds: 100)); - - expect(find.text('Home'), findsOneWidget); // Transition still in progress - - // Can't test directly for taps because route is interactive but offstage - // One ignore pointer for each of two overlay entries (ModalScope, ModalBarrier) on each of two routes - expect(find.byType(IgnorePointer, skipOffstage: false), findsNWidgets(4)); - final List ignorePointers = find.byType(IgnorePointer, skipOffstage: false).evaluate().toList(); - expect((ignorePointers.first.widget as IgnorePointer).ignoring, true); // Home modalBarrier - expect((ignorePointers[1].widget as IgnorePointer).ignoring, true); // Home modalScope - expect((ignorePointers[2].widget as IgnorePointer).ignoring, false); // Page 2 modalBarrier - expect((ignorePointers.last.widget as IgnorePointer).ignoring, false); // Page 2 modalScope - }); - testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async { final GlobalKey containerKey = GlobalKey(); diff --git a/packages/flutter_test/test/widget_tester_test.dart b/packages/flutter_test/test/widget_tester_test.dart index 6f1a91713aa8..c10992fbdcf2 100644 --- a/packages/flutter_test/test/widget_tester_test.dart +++ b/packages/flutter_test/test/widget_tester_test.dart @@ -440,7 +440,7 @@ void main() { await tester.tap(find.text('Next')); await tester.pump(); - await tester.pumpAndSettle(); + await tester.pump(const Duration(milliseconds: 400)); await tester.pageBack(); await tester.pump();