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 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'dart:convert';
import 'dart:developer' as developer;
......@@ -2335,7 +2336,7 @@ class _RouteEntry extends RouteTransitionRecord {
return page.canUpdate(routePage);
}
void handleAdd({ @required NavigatorState navigator}) {
void handleAdd({ @required NavigatorState navigator, @required Route<dynamic> previousPresent }) {
assert(currentState == _RouteLifecycle.add);
assert(navigator != null);
assert(navigator._debugLocked);
......@@ -2344,6 +2345,9 @@ class _RouteEntry extends RouteTransitionRecord {
route.install();
assert(route.overlayEntries.isNotEmpty);
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 }) {
......@@ -2377,12 +2381,14 @@ class _RouteEntry extends RouteTransitionRecord {
}
if (previousState == _RouteLifecycle.replace || previousState == _RouteLifecycle.pushReplace) {
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didReplace(newRoute: route, oldRoute: previousPresent);
navigator._observedRouteAdditions.add(
_NavigatorReplaceObservation(route, previousPresent)
);
} else {
assert(previousState == _RouteLifecycle.push);
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didPush(route, previousPresent);
navigator._observedRouteAdditions.add(
_NavigatorPushObservation(route, previousPresent)
);
}
}
......@@ -2396,8 +2402,9 @@ class _RouteEntry extends RouteTransitionRecord {
assert(navigator._debugLocked);
assert(route._navigator == navigator);
currentState = _RouteLifecycle.popping;
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didPop(route, previousPresent);
navigator._observedRouteDeletions.add(
_NavigatorPopObservation(route, previousPresent)
);
}
void handleRemoval({ @required NavigatorState navigator, @required Route<dynamic> previousPresent }) {
......@@ -2406,21 +2413,20 @@ class _RouteEntry extends RouteTransitionRecord {
assert(route._navigator == navigator);
currentState = _RouteLifecycle.removing;
if (_reportRemovalToObserver) {
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didRemove(route, previousPresent);
navigator._observedRouteDeletions.add(
_NavigatorRemoveObservation(route, previousPresent)
);
}
}
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();
currentState = _RouteLifecycle.idle;
if (isNewFirst) {
route.didChangeNext(null);
}
for (final NavigatorObserver observer in navigator.widget.observers)
observer.didPush(route, previousPresent);
}
void pop<T>(T result) {
......@@ -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.
class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
final GlobalKey<OverlayState> _overlayKey = GlobalKey<OverlayState>();
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.
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'Navigator Scope');
......@@ -2984,6 +3051,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
assert(rearrangeOverlay);
entry.handleAdd(
navigator: this,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
);
assert(entry.currentState == _RouteLifecycle.adding);
continue;
......@@ -2991,8 +3059,6 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
if (canRemoveOrAdd || next == null) {
entry.didAdd(
navigator: this,
previous: previous?.route,
previousPresent: _getRouteBefore(index - 1, _RouteEntry.isPresentPredicate)?.route,
isNewFirst: next == null
);
assert(entry.currentState == _RouteLifecycle.idle);
......@@ -3079,6 +3145,10 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
entry = previous;
previous = index > 0 ? _history[index - 1] : null;
}
// Informs navigator observers about route changes.
_flushObserverNotifications();
// Now that the list is clean, send the didChangeNext/didChangePrevious
// notifications.
_flushRouteAnnouncement();
......@@ -3102,6 +3172,23 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin {
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() {
int index = _history.length - 1;
while (index >= 0) {
......
......@@ -454,6 +454,45 @@ void main() {
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 {
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }),
......@@ -1892,12 +1931,13 @@ void main() {
});
group('Page api', (){
Widget buildNavigator(
Widget buildNavigator({
List<Page<dynamic>> pages,
PopPageCallback onPopPage, [
PopPageCallback onPopPage,
GlobalKey<NavigatorState> key,
TransitionDelegate<dynamic> transitionDelegate
]) {
TransitionDelegate<dynamic> transitionDelegate,
List<NavigatorObserver> observers = const <NavigatorObserver>[],
}) {
return MediaQuery(
data: MediaQueryData.fromWindow(WidgetsBinding.instance.window),
child: Localizations(
......@@ -1912,6 +1952,7 @@ void main() {
key: key,
pages: pages,
onPopPage: onPopPage,
observers: observers,
transitionDelegate: transitionDelegate ?? const DefaultTransitionDelegate<dynamic>(),
),
),
......@@ -1932,7 +1973,9 @@ void main() {
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('second'), findsNothing);
expect(find.text('initial'), findsNothing);
......@@ -1980,7 +2023,9 @@ void main() {
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);
myPages = <Page<dynamic>>[
......@@ -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
// page should chain with the third page. The animation of second page
// won't start until the third page finishes transition.
......@@ -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));
expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteOne.status, AnimationStatus.completed);
......@@ -2164,7 +2213,9 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
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('second'), findsNothing);
expect(find.text('initial'), findsNothing);
......@@ -2176,7 +2227,9 @@ void main() {
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
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.
expect(secondaryAnimationOfRouteThree.value, primaryAnimationOfRouteTwo.value);
expect(primaryAnimationOfRouteThree.status, AnimationStatus.completed);
......@@ -2232,7 +2285,9 @@ void main() {
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('initial'), findsNothing);
// Pushes two pageless routes to second page route
......@@ -2261,7 +2316,9 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name:'second'),
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();
expect(find.text('initial'), findsNothing);
expect(find.text('second'), findsNothing);
......@@ -2291,7 +2348,9 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name:'third'),
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
// transition. The routes should update without a pumpAndSettle
// Now the history should look like
......@@ -2361,7 +2420,9 @@ void main() {
}
// 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;
navigator.currentState.push(
MaterialPageRoute<void>(
......@@ -2376,7 +2437,9 @@ void main() {
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
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();
bool secondPageless1Completed = false;
navigator.currentState.push(
......@@ -2401,7 +2464,9 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name: 'second'),
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();
bool thirdPageless1Completed = false;
navigator.currentState.push(
......@@ -2423,7 +2488,9 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name: 'third'),
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.
expect(initialPageless1Completed, true);
expect(secondPageless1Completed, false);
......@@ -2433,7 +2500,9 @@ void main() {
myPages = <TestPage>[
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();
expect(secondPageless1Completed, true);
expect(secondPageless2Completed, true);
......@@ -2442,7 +2511,9 @@ void main() {
myPages = <TestPage>[
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);
await tester.pumpAndSettle();
expect(find.text('forth'), findsOneWidget);
......@@ -2460,7 +2531,14 @@ void main() {
}
// 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;
navigator.currentState.push(
MaterialPageRoute<void>(
......@@ -2475,7 +2553,14 @@ void main() {
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
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;
navigator.currentState.push(
MaterialPageRoute<void>(
......@@ -2499,7 +2584,14 @@ void main() {
const TestPage(key: ValueKey<String>('2'), name: 'second'),
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;
navigator.currentState.push(
MaterialPageRoute<void>(
......@@ -2520,7 +2612,14 @@ void main() {
const TestPage(key: ValueKey<String>('3'), name: 'third'),
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.
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
......@@ -2530,7 +2629,14 @@ void main() {
myPages = <TestPage>[
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();
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
......@@ -2540,7 +2646,14 @@ void main() {
myPages = <TestPage>[
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();
expect(initialPageless1Completed, false);
expect(secondPageless1Completed, false);
......@@ -2559,20 +2672,26 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
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.
myPages = <TestPage>[
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.
myPages = <TestPage>[
const TestPage(key: ValueKey<String>('1'), name: 'initial'),
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.
expect(tester.takeException(), isNull);
......@@ -2590,25 +2709,121 @@ void main() {
myPages.removeWhere((Page<dynamic> page) => route.settings == page);
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.
myPages = <TestPage>[
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.
myPages = <TestPage>[
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.
expect(tester.takeException(), isNull);
await tester.pumpAndSettle();
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