Adam Barth ffe14b0d27 Navigator.push and Navigator.pushNamed should return Futures (#6333)
These futures complete when the route is popped off the navigator. This
generalizes and simplifies a mechanism already in place for dialogs and
menus.

Fixes #5283
2016-10-14 16:05:45 -07:00

390 lines
9.4 KiB
Dart

// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
final List<String> results = <String>[];
Set<TestRoute> routes = new HashSet<TestRoute>();
class TestRoute extends LocalHistoryRoute<String> {
TestRoute(this.name);
final String name;
@override
List<OverlayEntry> get overlayEntries => _entries;
List<OverlayEntry> _entries = <OverlayEntry>[];
void log(String s) {
results.add('$name: $s');
}
@override
void install(OverlayEntry insertionPoint) {
log('install');
OverlayEntry entry = new OverlayEntry(
builder: (BuildContext context) => new Container(),
opaque: true
);
_entries.add(entry);
navigator.overlay?.insert(entry, above: insertionPoint);
routes.add(this);
super.install(insertionPoint);
}
@override
void didPush() {
log('didPush');
super.didPush();
}
@override
void didReplace(@checked TestRoute oldRoute) {
log('didReplace ${oldRoute.name}');
super.didReplace(oldRoute);
}
@override
bool didPop(String result) {
log('didPop $result');
bool returnValue;
if (returnValue = super.didPop(result))
dispose();
return returnValue;
}
@override
void didPopNext(@checked TestRoute nextRoute) {
log('didPopNext ${nextRoute.name}');
super.didPopNext(nextRoute);
}
@override
void didChangeNext(@checked TestRoute nextRoute) {
log('didChangeNext ${nextRoute?.name}');
super.didChangeNext(nextRoute);
}
@override
void dispose() {
log('dispose');
_entries.forEach((OverlayEntry entry) { entry.remove(); });
_entries.clear();
routes.remove(this);
super.dispose();
}
}
Future<Null> runNavigatorTest(
WidgetTester tester,
NavigatorState host,
VoidCallback test,
List<String> expectations
) async {
expect(host, isNotNull);
test();
expect(results, equals(expectations));
results.clear();
await tester.pump();
}
void main() {
testWidgets('Route management - push, replace, pop', (WidgetTester tester) async {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new Navigator(
key: navigatorKey,
onGenerateRoute: (_) => new TestRoute('initial')
));
NavigatorState host = navigatorKey.currentState;
await runNavigatorTest(
tester,
host,
() { },
<String>[
'initial: install',
'initial: didPush',
'initial: didChangeNext null',
]
);
TestRoute second;
await runNavigatorTest(
tester,
host,
() { host.push(second = new TestRoute('second')); },
<String>[
'second: install',
'second: didPush',
'second: didChangeNext null',
'initial: didChangeNext second',
]
);
await runNavigatorTest(
tester,
host,
() { host.push(new TestRoute('third')); },
<String>[
'third: install',
'third: didPush',
'third: didChangeNext null',
'second: didChangeNext third',
]
);
await runNavigatorTest(
tester,
host,
() { host.replace(oldRoute: second, newRoute: new TestRoute('two')); },
<String>[
'two: install',
'two: didReplace second',
'two: didChangeNext third',
'initial: didChangeNext two',
'second: dispose',
]
);
await runNavigatorTest(
tester,
host,
() { host.pop('hello'); },
<String>[
'third: didPop hello',
'third: dispose',
'two: didPopNext third',
]
);
await runNavigatorTest(
tester,
host,
() { host.pop('good bye'); },
<String>[
'two: didPop good bye',
'two: dispose',
'initial: didPopNext two',
]
);
await tester.pumpWidget(new Container());
expect(results, equals(<String>['initial: dispose']));
expect(routes.isEmpty, isTrue);
results.clear();
});
testWidgets('Route management - push, remove, pop', (WidgetTester tester) async {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new Navigator(
key: navigatorKey,
onGenerateRoute: (_) => new TestRoute('first')
));
NavigatorState host = navigatorKey.currentState;
await runNavigatorTest(
tester,
host,
() { },
<String>[
'first: install',
'first: didPush',
'first: didChangeNext null',
]
);
TestRoute second;
await runNavigatorTest(
tester,
host,
() { host.push(second = new TestRoute('second')); },
<String>[
'second: install',
'second: didPush',
'second: didChangeNext null',
'first: didChangeNext second',
]
);
await runNavigatorTest(
tester,
host,
() { host.push(new TestRoute('third')); },
<String>[
'third: install',
'third: didPush',
'third: didChangeNext null',
'second: didChangeNext third',
]
);
await runNavigatorTest(
tester,
host,
() { host.removeRouteBelow(second); },
<String>[
'first: dispose',
]
);
await runNavigatorTest(
tester,
host,
() { host.pop('good bye'); },
<String>[
'third: didPop good bye',
'third: dispose',
'second: didPopNext third',
]
);
await runNavigatorTest(
tester,
host,
() { host.push(new TestRoute('three')); },
<String>[
'three: install',
'three: didPush',
'three: didChangeNext null',
'second: didChangeNext three',
]
);
TestRoute four;
await runNavigatorTest(
tester,
host,
() { host.push(four = new TestRoute('four')); },
<String>[
'four: install',
'four: didPush',
'four: didChangeNext null',
'three: didChangeNext four',
]
);
await runNavigatorTest(
tester,
host,
() { host.removeRouteBelow(four); },
<String>[
'second: didChangeNext four',
'three: dispose',
]
);
await runNavigatorTest(
tester,
host,
() { host.pop('the end'); },
<String>[
'four: didPop the end',
'four: dispose',
'second: didPopNext four',
]
);
await tester.pumpWidget(new Container());
expect(results, equals(<String>['second: dispose']));
expect(routes.isEmpty, isTrue);
results.clear();
});
testWidgets('Route management - push, replace, popUntil', (WidgetTester tester) async {
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new Navigator(
key: navigatorKey,
onGenerateRoute: (_) => new TestRoute('A')
));
NavigatorState host = navigatorKey.currentState;
await runNavigatorTest(
tester,
host,
() { },
<String>[
'A: install',
'A: didPush',
'A: didChangeNext null',
]
);
await runNavigatorTest(
tester,
host,
() { host.push(new TestRoute('B')); },
<String>[
'B: install',
'B: didPush',
'B: didChangeNext null',
'A: didChangeNext B',
]
);
TestRoute routeC;
await runNavigatorTest(
tester,
host,
() { host.push(routeC = new TestRoute('C')); },
<String>[
'C: install',
'C: didPush',
'C: didChangeNext null',
'B: didChangeNext C',
]
);
TestRoute routeB;
await runNavigatorTest(
tester,
host,
() { host.replaceRouteBelow(anchorRoute: routeC, newRoute: routeB = new TestRoute('b')); },
<String>[
'b: install',
'b: didReplace B',
'b: didChangeNext C',
'A: didChangeNext b',
'B: dispose',
]
);
await runNavigatorTest(
tester,
host,
() { host.popUntil((Route<dynamic> route) => route == routeB); },
<String>[
'C: didPop null',
'C: dispose',
'b: didPopNext C',
]
);
await tester.pumpWidget(new Container());
expect(results, equals(<String>['A: dispose', 'b: dispose']));
expect(routes.isEmpty, isTrue);
results.clear();
});
testWidgets('Route localHistory - popUntil', (WidgetTester tester) async {
TestRoute routeA = new TestRoute('A');
routeA.addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () { routeA.log('onRemove 0'); }
));
routeA.addLocalHistoryEntry(new LocalHistoryEntry(
onRemove: () { routeA.log('onRemove 1'); }
));
GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
await tester.pumpWidget(new Navigator(
key: navigatorKey,
onGenerateRoute: (_) => routeA
));
NavigatorState host = navigatorKey.currentState;
await runNavigatorTest(
tester,
host,
() { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
<String>[
'A: install',
'A: didPush',
'A: didChangeNext null',
'A: didPop null',
'A: onRemove 1',
'A: didPop null',
'A: onRemove 0',
]
);
await runNavigatorTest(
tester,
host,
() { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
<String>[
]
);
});
}