1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/// @docImport 'widget_tester.dart';
6library;
7
8import 'package:flutter/widgets.dart';
9
10import 'widget_tester.dart';
11
12// Examples can assume:
13// final TransitionDurationObserver transitionDurationObserver = TransitionDurationObserver();
14
15/// Tracks the duration of the most recent page transition.
16///
17/// Pass an instance to [Navigator.observers] or
18/// [WidgetsApp.navigatorObservers], then access [transitionDuration].
19class TransitionDurationObserver extends NavigatorObserver {
20 Duration? _transitionDuration;
21
22 /// The total duration of the most recent page transition.
23 ///
24 /// When called during a page transition, it will return the full duration of
25 /// the currently active page transition. If called immediately after a call
26 /// to `Navigator.pop`, for example, it will return the duration of the
27 /// transition triggered by that call. If called halfway through a page
28 /// transition, it will still return the full duration, not half.
29 ///
30 /// To pump until the route transition is finished and the previous route is
31 /// completely gone, use the following:
32 ///
33 /// {@tool snippet}
34 /// ```dart
35 /// testWidgets('MyWidget', (WidgetTester tester) async {
36 /// // ...Pump the app and start a page transition, then:
37 /// await tester.pump();
38 /// await tester.pump(transitionDurationObserver.transitionDuration + const Duration(milliseconds: 1));
39 /// });
40 /// ```
41 /// {@end-tool}
42 ///
43 /// Throws if there has never been a page transition.
44 ///
45 /// See also:
46 ///
47 /// * [pumpPastTransition], which handles pumping past the current page
48 /// transition.
49 Duration get transitionDuration {
50 if (_transitionDuration == null) {
51 throw FlutterError(
52 'No route transition has occurred, but the transition duration was requested.',
53 );
54 }
55 return _transitionDuration!;
56 }
57
58 /// Pumps the minimum number of frames required for the current page
59 /// transition to run from start to finish.
60 ///
61 /// Typically used immediately after starting a page transition in order to
62 /// wait until the first frame in which the outgoing page is no longer
63 /// visible.
64 ///
65 /// This does not consider whether or not a page transition is currently
66 /// running. If it's called in the middle of a page transition, for example,
67 /// it will still pump for the full duration of the page transition, not just
68 /// for the remaining duration.
69 ///
70 /// {@tool snippet}
71 /// ```dart
72 /// testWidgets('MyWidget', (WidgetTester tester) async {
73 /// // ...Pump an app with two pages, then:
74 /// expect(find.text('Page 1'), findsOneWidget);
75 /// expect(find.text('Page 2'), findsNothing);
76 ///
77 /// await tester.tap(find.text('Next'));
78 ///
79 /// await transitionDurationObserver.pumpPastTransition(tester);
80 ///
81 /// expect(find.text('Page 1'), findsNothing);
82 /// expect(find.text('Page 2'), findsOneWidget);
83 /// });
84 /// ```
85 /// {@end-tool}
86 ///
87 /// See also:
88 ///
89 /// * [transitionDuration], which directly returns the [Duration] of the page
90 /// transition.
91 Future<void> pumpPastTransition(WidgetTester tester) async {
92 // The first pump is required to begin the page transition animation and
93 // make the application no longer idle.
94 await tester.pump();
95 // Pumping for the full transitionDuration would move to the last frame of
96 // the page transition, so pump one more frame after that.
97 await tester.pump(transitionDuration + const Duration(milliseconds: 1));
98 }
99
100 @override
101 void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
102 // When pushing, the incoming route determines the transition duration.
103 if (route is TransitionRoute) {
104 _transitionDuration = route.transitionDuration;
105 }
106 super.didPush(route, previousRoute);
107 }
108
109 @override
110 void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
111 // When popping, the outgoing route's reverseTransitionDuration determines
112 // the transition duration.
113 if (route is TransitionRoute) {
114 _transitionDuration = route.reverseTransitionDuration;
115 }
116 super.didPop(route, previousRoute);
117 }
118
119 @override
120 void didReplace({Route<dynamic>? oldRoute, Route<dynamic>? newRoute}) {
121 // When replacing, the new route determines the transition duration.
122 if (newRoute is TransitionRoute) {
123 _transitionDuration = newRoute.transitionDuration;
124 }
125 super.didReplace(oldRoute: oldRoute, newRoute: newRoute);
126 }
127
128 // didRemove is not included because it does not trigger a page transition.
129}
130