Unverified Commit afbb80b2 authored by chunhtai's avatar chunhtai Committed by GitHub

fix navigator observer announcement order (#57605)

* fix navigator observer announcement order

* addressing review comments

* update

* fix analyzer
parent 3e273b14
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'dart:developer' as developer; import 'dart:developer' as developer;
...@@ -2335,7 +2336,7 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2335,7 +2336,7 @@ class _RouteEntry extends RouteTransitionRecord {
return page.canUpdate(routePage); return page.canUpdate(routePage);
} }
void handleAdd({ @required NavigatorState navigator}) { void handleAdd({ @required NavigatorState navigator, @required Route<dynamic> previousPresent }) {
assert(currentState == _RouteLifecycle.add); assert(currentState == _RouteLifecycle.add);
assert(navigator != null); assert(navigator != null);
assert(navigator._debugLocked); assert(navigator._debugLocked);
...@@ -2344,6 +2345,9 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2344,6 +2345,9 @@ class _RouteEntry extends RouteTransitionRecord {
route.install(); route.install();
assert(route.overlayEntries.isNotEmpty); assert(route.overlayEntries.isNotEmpty);
currentState = _RouteLifecycle.adding; currentState = _RouteLifecycle.adding;
navigator._observedRouteAdditions.add(
_NavigatorPushObservation(route, previousPresent)
);
} }
void handlePush({ @required NavigatorState navigator, @required bool isNewFirst, @required Route<dynamic> previous, @required Route<dynamic> previousPresent }) { void handlePush({ @required NavigatorState navigator, @required bool isNewFirst, @required Route<dynamic> previous, @required Route<dynamic> previousPresent }) {
...@@ -2377,12 +2381,14 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2377,12 +2381,14 @@ class _RouteEntry extends RouteTransitionRecord {
} }
if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) { if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
for (final NavigatorObserver observer in navigator.widget.observers) navigator._observedRouteAdditions.add(
observer.didReplace(newRoute: route, oldRoute: previousPresent); _NavigatorReplaceObservation(route, previousPresent)
);
} else { } else {
assert(previousState == _RouteLifecycle.push); assert(previousState == _RouteLifecycle.push);
for (final NavigatorObserver observer in navigator.widget.observers) navigator._observedRouteAdditions.add(
observer.didPush(route, previousPresent); _NavigatorPushObservation(route, previousPresent)
);
} }
} }
...@@ -2396,8 +2402,9 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2396,8 +2402,9 @@ class _RouteEntry extends RouteTransitionRecord {
assert(navigator._debugLocked); assert(navigator._debugLocked);
assert(route._navigator == navigator); assert(route._navigator == navigator);
currentState = _RouteLifecycle.popping; currentState = _RouteLifecycle.popping;
for (final NavigatorObserver observer in navigator.widget.observers) navigator._observedRouteDeletions.add(
observer.didPop(route, previousPresent); _NavigatorPopObservation(route, previousPresent)
);
} }
void handleRemoval({ @required NavigatorState navigator, @required Route<dynamic> previousPresent }) { void handleRemoval({ @required NavigatorState navigator, @required Route<dynamic> previousPresent }) {
...@@ -2406,21 +2413,20 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2406,21 +2413,20 @@ class _RouteEntry extends RouteTransitionRecord {
assert(route._navigator == navigator); assert(route._navigator == navigator);
currentState = _RouteLifecycle.removing; currentState = _RouteLifecycle.removing;
if (_reportRemovalToObserver) { if (_reportRemovalToObserver) {
for (final NavigatorObserver observer in navigator.widget.observers) navigator._observedRouteDeletions.add(
observer.didRemove(route, previousPresent); _NavigatorRemoveObservation(route, previousPresent)
);
} }
} }
bool doingPop = false; bool doingPop = false;
void didAdd({ @required NavigatorState navigator, @required bool isNewFirst, @required Route<dynamic> previous, @required Route<dynamic> previousPresent }) { void didAdd({ @required NavigatorState navigator, @required bool isNewFirst}) {
route.didAdd(); route.didAdd();
currentState = _RouteLifecycle.idle; currentState = _RouteLifecycle.idle;
if (isNewFirst) { if (isNewFirst) {
route.didChangeNext(null); route.didChangeNext(null);
} }
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didPush(route, previousPresent);
} }
void pop<T>(T result) { void pop<T>(T result) {
...@@ -2579,10 +2585,71 @@ class _RouteEntry extends RouteTransitionRecord { ...@@ -2579,10 +2585,71 @@ class _RouteEntry extends RouteTransitionRecord {
} }
} }
abstract class _NavigatorObservation {
_NavigatorObservation(
this.primaryRoute,
this.secondaryRoute,
);
final Route<dynamic> primaryRoute;
final Route<dynamic> secondaryRoute;
void notify(NavigatorObserver observer);
}
class _NavigatorPushObservation extends _NavigatorObservation {
_NavigatorPushObservation(
Route<dynamic> primaryRoute,
Route<dynamic> secondaryRoute
) : super(primaryRoute, secondaryRoute);
@override
void notify(NavigatorObserver observer) {
observer.didPush(primaryRoute, secondaryRoute);
}
}
class _NavigatorPopObservation extends _NavigatorObservation {
_NavigatorPopObservation(
Route<dynamic> primaryRoute,
Route<dynamic> secondaryRoute
) : super(primaryRoute, secondaryRoute);
@override
void notify(NavigatorObserver observer) {
observer.didPop(primaryRoute, secondaryRoute);
}
}
class _NavigatorRemoveObservation extends _NavigatorObservation {
_NavigatorRemoveObservation(
Route<dynamic> primaryRoute,
Route<dynamic> secondaryRoute
) : super(primaryRoute, secondaryRoute);
@override
void notify(NavigatorObserver observer) {
observer.didRemove(primaryRoute, secondaryRoute);
}
}
class _NavigatorReplaceObservation extends _NavigatorObservation {
_NavigatorReplaceObservation(
Route<dynamic> primaryRoute,
Route<dynamic> secondaryRoute
) : super(primaryRoute, secondaryRoute);
@override
void notify(NavigatorObserver observer) {
observer.didReplace(newRoute: primaryRoute, oldRoute: secondaryRoute);
}
}
/// The state for a [Navigator] widget. /// The state for a [Navigator] widget.
class NavigatorState extends State<Navigator> with TickerProviderStateMixin { class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>(); final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>();
List<_RouteEntry> _history = <_RouteEntry>[]; List<_RouteEntry> _history = <_RouteEntry>[];
final Queue<_NavigatorObservation> _observedRouteAdditions = Queue<_NavigatorObservation>();
final Queue<_NavigatorObservation> _observedRouteDeletions = Queue<_NavigatorObservation>();
/// The [FocusScopeNode] for the [FocusScope] that encloses the routes. /// The [FocusScopeNode] for the [FocusScope] that encloses the routes.
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope'); final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
...@@ -2984,6 +3051,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -2984,6 +3051,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert(rearrangeOverlay); assert(rearrangeOverlay);
entry.handleAdd( entry.handleAdd(
navigator: this, navigator: this,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
); );
assert(entry.currentState == _RouteLifecycle.adding); assert(entry.currentState == _RouteLifecycle.adding);
continue; continue;
...@@ -2991,8 +3059,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -2991,8 +3059,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
if (canRemoveOrAdd || next == null) { if (canRemoveOrAdd || next == null) {
entry.didAdd( entry.didAdd(
navigator: this, navigator: this,
previous: previous?.route,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
isNewFirst: next == null isNewFirst: next == null
); );
assert(entry.currentState == _RouteLifecycle.idle); assert(entry.currentState == _RouteLifecycle.idle);
...@@ -3079,6 +3145,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -3079,6 +3145,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
entry = previous; entry = previous;
previous = index > 0 ? _history[index - 1] : null; previous = index > 0 ? _history[index - 1] : null;
} }
// Informs navigator observers about route changes.
_flushObserverNotifications();
// Now that the list is clean, send the didChangeNext/didChangePrevious // Now that the list is clean, send the didChangeNext/didChangePrevious
// notifications. // notifications.
_flushRouteAnnouncement(); _flushRouteAnnouncement();
...@@ -3102,6 +3172,23 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin { ...@@ -3102,6 +3172,23 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
overlay?.rearrange(_allRouteOverlayEntries); overlay?.rearrange(_allRouteOverlayEntries);
} }
void _flushObserverNotifications() {
if (widget.observers.isEmpty) {
_observedRouteDeletions.clear();
_observedRouteAdditions.clear();
return;
}
while (_observedRouteAdditions.isNotEmpty) {
final _NavigatorObservation observation = _observedRouteAdditions.removeLast();
widget.observers.forEach(observation.notify);
}
while (_observedRouteDeletions.isNotEmpty) {
final _NavigatorObservation observation = _observedRouteDeletions.removeFirst();
widget.observers.forEach(observation.notify);
}
}
void _flushRouteAnnouncement() { void _flushRouteAnnouncement() {
int index = _history.length - 1; int index = _history.length - 1;
while (index >= 0) { while (index >= 0) {
......
...@@ -454,6 +454,45 @@ void main() { ...@@ -454,6 +454,45 @@ void main() {
expect(isPopped, isFalse); expect(isPopped, isFalse);
}); });
testWidgets('initial route trigger observer in the right order', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/' : (BuildContext context) => const Text('/'),
'/A': (BuildContext context) => const Text('A'),
'/A/B': (BuildContext context) => const Text('B'),
};
final List<NavigatorObservation> observations = <NavigatorObservation>[];
final TestObserver observer = TestObserver()
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
// Pushes the initial route.
observations.add(
NavigatorObservation(
current: route?.settings?.name,
previous: previousRoute?.settings?.name,
operation: 'push'
)
);
};
await tester.pumpWidget(MaterialApp(
routes: routes,
initialRoute: '/A/B',
navigatorObservers: <NavigatorObserver>[observer],
));
expect(observations.length, 3);
expect(observations[0].operation, 'push');
expect(observations[0].current, '/');
expect(observations[0].previous, isNull);
expect(observations[1].operation, 'push');
expect(observations[1].current, '/A');
expect(observations[1].previous, '/');
expect(observations[2].operation, 'push');
expect(observations[2].current, '/A/B');
expect(observations[2].previous, '/A');
});
testWidgets('replaceNamed replaces', (WidgetTester tester) async { testWidgets('replaceNamed replaces', (WidgetTester tester) async {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }), '/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }),
...@@ -1892,12 +1931,13 @@ void main() { ...@@ -1892,12 +1931,13 @@ void main() {
}); });
group('Page api', (){ group('Page api', (){
Widget buildNavigator( Widget buildNavigator({
List<Page<dynamic>> pages, List<Page<dynamic>> pages,
PopPageCallback onPopPage, [ PopPageCallback onPopPage,
GlobalKey<NavigatorState> key, GlobalKey<NavigatorState> key,
TransitionDelegate<dynamic> transitionDelegate TransitionDelegate<dynamic> transitionDelegate,
]) { List<NavigatorObserver> observers = const <NavigatorObserver>[],
}) {
return MediaQuery( return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window), data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations( child: Localizations(
...@@ -1912,6 +1952,7 @@ void main() { ...@@ -1912,6 +1952,7 @@ void main() {
key: key, key: key,
pages: pages, pages: pages,
onPopPage: onPopPage, onPopPage: onPopPage,
observers: observers,
transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(), transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
), ),
), ),
...@@ -1932,7 +1973,9 @@ void main() { ...@@ -1932,7 +1973,9 @@ void main() {
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
expect(find.text('third'), findsOneWidget); expect(find.text('third'), findsOneWidget);
expect(find.text('second'), findsNothing); expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
...@@ -1980,7 +2023,9 @@ void main() { ...@@ -1980,7 +2023,9 @@ void main() {
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
myPages = <Page<dynamic>>[ myPages = <Page<dynamic>>[
...@@ -2028,7 +2073,9 @@ void main() { ...@@ -2028,7 +2073,9 @@ void main() {
) )
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// The third page is transitioning, and the secondary animation of first // The third page is transitioning, and the secondary animation of first
// page should chain with the third page. The animation of second page // page should chain with the third page. The animation of second page
// won't start until the third page finishes transition. // won't start until the third page finishes transition.
...@@ -2091,7 +2138,9 @@ void main() { ...@@ -2091,7 +2138,9 @@ void main() {
), ),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
await tester.pump(const Duration(milliseconds: 30)); await tester.pump(const Duration(milliseconds: 30));
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value); expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed); expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
...@@ -2164,7 +2213,9 @@ void main() { ...@@ -2164,7 +2213,9 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page); myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
expect(find.text('third'), findsOneWidget); expect(find.text('third'), findsOneWidget);
expect(find.text('second'), findsNothing); expect(find.text('second'), findsNothing);
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
...@@ -2176,7 +2227,9 @@ void main() { ...@@ -2176,7 +2227,9 @@ void main() {
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed); expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
myPages = myPages.reversed.toList(); myPages = myPages.reversed.toList();
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Reversed routes are still chained up correctly. // Reversed routes are still chained up correctly.
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value); expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed); expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
...@@ -2232,7 +2285,9 @@ void main() { ...@@ -2232,7 +2285,9 @@ void main() {
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
expect(find.text('second'), findsOneWidget); expect(find.text('second'), findsOneWidget);
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
// Pushes two pageless routes to second page route // Pushes two pageless routes to second page route
...@@ -2261,7 +2316,9 @@ void main() { ...@@ -2261,7 +2316,9 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name:'second'), const TestPage(key: ValueKey<String>('2'), name:'second'),
const TestPage(key: ValueKey<String>('3'), name:'third'), const TestPage(key: ValueKey<String>('3'), name:'third'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('initial'), findsNothing); expect(find.text('initial'), findsNothing);
expect(find.text('second'), findsNothing); expect(find.text('second'), findsNothing);
...@@ -2291,7 +2348,9 @@ void main() { ...@@ -2291,7 +2348,9 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name:'third'), const TestPage(key: ValueKey<String>('3'), name:'third'),
const TestPage(key: ValueKey<String>('2'), name:'second'), const TestPage(key: ValueKey<String>('2'), name:'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Swaps the order without any adding or removing should not trigger any // Swaps the order without any adding or removing should not trigger any
// transition. The routes should update without a pumpAndSettle // transition. The routes should update without a pumpAndSettle
// Now the history should look like // Now the history should look like
...@@ -2361,7 +2420,9 @@ void main() { ...@@ -2361,7 +2420,9 @@ void main() {
} }
// Add initial page route with one pageless route. // Add initial page route with one pageless route.
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
bool initialPageless1Completed = false; bool initialPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
MaterialPageRoute<void>( MaterialPageRoute<void>(
...@@ -2376,7 +2437,9 @@ void main() { ...@@ -2376,7 +2437,9 @@ void main() {
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
bool secondPageless1Completed = false; bool secondPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
...@@ -2401,7 +2464,9 @@ void main() { ...@@ -2401,7 +2464,9 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
bool thirdPageless1Completed = false; bool thirdPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
...@@ -2423,7 +2488,9 @@ void main() { ...@@ -2423,7 +2488,9 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// The pageless route of initial page route should be completed. // The pageless route of initial page route should be completed.
expect(initialPageless1Completed, true); expect(initialPageless1Completed, true);
expect(secondPageless1Completed, false); expect(secondPageless1Completed, false);
...@@ -2433,7 +2500,9 @@ void main() { ...@@ -2433,7 +2500,9 @@ void main() {
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(secondPageless1Completed, true); expect(secondPageless1Completed, true);
expect(secondPageless2Completed, true); expect(secondPageless2Completed, true);
...@@ -2442,7 +2511,9 @@ void main() { ...@@ -2442,7 +2511,9 @@ void main() {
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('4'), name: 'forth'), const TestPage(key: ValueKey<String>('4'), name: 'forth'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
expect(thirdPageless1Completed, true); expect(thirdPageless1Completed, true);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('forth'), findsOneWidget); expect(find.text('forth'), findsOneWidget);
...@@ -2460,7 +2531,14 @@ void main() { ...@@ -2460,7 +2531,14 @@ void main() {
} }
// Add initial page route with one pageless route. // Add initial page route with one pageless route.
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
bool initialPageless1Completed = false; bool initialPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
MaterialPageRoute<void>( MaterialPageRoute<void>(
...@@ -2475,7 +2553,14 @@ void main() { ...@@ -2475,7 +2553,14 @@ void main() {
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
bool secondPageless1Completed = false; bool secondPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
MaterialPageRoute<void>( MaterialPageRoute<void>(
...@@ -2499,7 +2584,14 @@ void main() { ...@@ -2499,7 +2584,14 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
bool thirdPageless1Completed = false; bool thirdPageless1Completed = false;
navigator.currentState.push( navigator.currentState.push(
MaterialPageRoute<void>( MaterialPageRoute<void>(
...@@ -2520,7 +2612,14 @@ void main() { ...@@ -2520,7 +2612,14 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
// The pageless route of initial page route should be removed without complete. // The pageless route of initial page route should be removed without complete.
expect(initialPageless1Completed, false); expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false); expect(secondPageless1Completed, false);
...@@ -2530,7 +2629,14 @@ void main() { ...@@ -2530,7 +2629,14 @@ void main() {
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('3'), name: 'third'), const TestPage(key: ValueKey<String>('3'), name: 'third'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(initialPageless1Completed, false); expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false); expect(secondPageless1Completed, false);
...@@ -2540,7 +2646,14 @@ void main() { ...@@ -2540,7 +2646,14 @@ void main() {
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('4'), name: 'forth'), const TestPage(key: ValueKey<String>('4'), name: 'forth'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator, transitionDelegate)); await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
transitionDelegate: transitionDelegate
)
);
await tester.pump(); await tester.pump();
expect(initialPageless1Completed, false); expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false); expect(secondPageless1Completed, false);
...@@ -2559,20 +2672,26 @@ void main() { ...@@ -2559,20 +2672,26 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page); myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Pops the second page route. // Pops the second page route.
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Re-push the second page again before it finishes popping. // Re-push the second page again before it finishes popping.
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
const TestPage(key: ValueKey<String>('2'), name: 'second'), const TestPage(key: ValueKey<String>('2'), name: 'second'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// It should not crash the app. // It should not crash the app.
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
...@@ -2590,25 +2709,121 @@ void main() { ...@@ -2590,25 +2709,121 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page); myPages.removeWhere((Page<dynamic> page) => route.settings == page);
return route.didPop(result); return route.didPop(result);
} }
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Pops the second page route. // Pops the second page route.
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// Updates the pages again before second page finishes popping. // Updates the pages again before second page finishes popping.
myPages = <TestPage>[ myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'), const TestPage(key: ValueKey<String>('1'), name: 'initial'),
]; ];
await tester.pumpWidget(buildNavigator(myPages, onPopPage, navigator)); await tester.pumpWidget(
buildNavigator(pages: myPages, onPopPage: onPopPage, key: navigator)
);
// It should not crash the app. // It should not crash the app.
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.text('initial'), findsOneWidget); expect(find.text('initial'), findsOneWidget);
}); });
testWidgets('pages remove and add trigger observer in the right order', (WidgetTester tester) async {
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
List<TestPage> myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name:'first'),
const TestPage(key: ValueKey<String>('2'), name:'second'),
const TestPage(key: ValueKey<String>('3'), name:'third'),
];
final List<NavigatorObservation> observations = <NavigatorObservation>[];
final TestObserver observer = TestObserver()
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
observations.add(
NavigatorObservation(
current: route?.settings?.name,
previous: previousRoute?.settings?.name,
operation: 'push'
)
);
}
..onRemoved = (Route<dynamic> route, Route<dynamic> previousRoute) {
observations.add(
NavigatorObservation(
current: route?.settings?.name,
previous: previousRoute?.settings?.name,
operation: 'remove'
)
);
};
bool onPopPage(Route<dynamic> route, dynamic result) => false;
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
observers: <NavigatorObserver>[observer],
)
);
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('4'), name:'forth'),
const TestPage(key: ValueKey<String>('5'), name:'fifth'),
];
await tester.pumpWidget(
buildNavigator(
pages: myPages,
onPopPage: onPopPage,
key: navigator,
observers: <NavigatorObserver>[observer],
)
);
await tester.pumpAndSettle();
expect(observations.length, 8);
// Initial routes are pushed.
expect(observations[0].operation, 'push');
expect(observations[0].current, 'first');
expect(observations[0].previous, isNull);
expect(observations[1].operation, 'push');
expect(observations[1].current, 'second');
expect(observations[1].previous, 'first');
expect(observations[2].operation, 'push');
expect(observations[2].current, 'third');
expect(observations[2].previous, 'second');
// Pages are updated.
// New routes are pushed before removing the initial routes.
expect(observations[3].operation, 'push');
expect(observations[3].current, 'forth');
expect(observations[3].previous, 'third');
expect(observations[4].operation, 'push');
expect(observations[4].current, 'fifth');
expect(observations[4].previous, 'forth');
// Initial routes are removed.
expect(observations[5].operation, 'remove');
expect(observations[5].current, 'third');
expect(observations[5].previous, isNull);
expect(observations[6].operation, 'remove');
expect(observations[6].current, 'second');
expect(observations[6].previous, isNull);
expect(observations[7].operation, 'remove');
expect(observations[7].current, 'first');
expect(observations[7].previous, isNull);
});
}); });
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment