1202 lines
43 KiB
Dart
1202 lines
43 KiB
Dart
// Copyright 2014 The Flutter 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:ui';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'observer_tester.dart';
|
|
import 'semantics_tester.dart';
|
|
|
|
class FirstWidget extends StatelessWidget {
|
|
const FirstWidget({ Key key }) : super(key: key);
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
Navigator.pushNamed(context, '/second');
|
|
},
|
|
child: Container(
|
|
color: const Color(0xFFFFFF00),
|
|
child: const Text('X'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SecondWidget extends StatefulWidget {
|
|
const SecondWidget({ Key key }) : super(key: key);
|
|
@override
|
|
SecondWidgetState createState() => SecondWidgetState();
|
|
}
|
|
|
|
class SecondWidgetState extends State<SecondWidget> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: () => Navigator.pop(context),
|
|
child: Container(
|
|
color: const Color(0xFFFF00FF),
|
|
child: const Text('Y'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
typedef ExceptionCallback = void Function(dynamic exception);
|
|
|
|
class ThirdWidget extends StatelessWidget {
|
|
const ThirdWidget({ Key key, this.targetKey, this.onException }) : super(key: key);
|
|
|
|
final Key targetKey;
|
|
final ExceptionCallback onException;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
key: targetKey,
|
|
onTap: () {
|
|
try {
|
|
Navigator.of(context);
|
|
} catch (e) {
|
|
onException(e);
|
|
}
|
|
},
|
|
behavior: HitTestBehavior.opaque,
|
|
);
|
|
}
|
|
}
|
|
|
|
class OnTapPage extends StatelessWidget {
|
|
const OnTapPage({ Key key, this.id, this.onTap }) : super(key: key);
|
|
|
|
final String id;
|
|
final VoidCallback onTap;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text('Page $id')),
|
|
body: GestureDetector(
|
|
onTap: onTap,
|
|
behavior: HitTestBehavior.opaque,
|
|
child: Container(
|
|
child: Center(
|
|
child: Text(id, style: Theme.of(context).textTheme.display2),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('Can navigator navigate to and from a stateful widget', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => const FirstWidget(), // X
|
|
'/second': (BuildContext context) => const SecondWidget(), // Y
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y', skipOffstage: false), findsNothing);
|
|
|
|
await tester.tap(find.text('X'));
|
|
await tester.pump();
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y', skipOffstage: false), isOffstage);
|
|
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('X'), findsNothing);
|
|
expect(find.text('X', skipOffstage: false), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('Y'));
|
|
expect(find.text('X'), findsNothing);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump();
|
|
await tester.pump();
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y'), findsOneWidget);
|
|
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('X'), findsOneWidget);
|
|
expect(find.text('Y', skipOffstage: false), findsNothing);
|
|
});
|
|
|
|
testWidgets('Navigator.of fails gracefully when not found in context', (WidgetTester tester) async {
|
|
const Key targetKey = Key('foo');
|
|
dynamic exception;
|
|
final Widget widget = ThirdWidget(
|
|
targetKey: targetKey,
|
|
onException: (dynamic e) {
|
|
exception = e;
|
|
},
|
|
);
|
|
await tester.pumpWidget(widget);
|
|
await tester.tap(find.byKey(targetKey));
|
|
expect(exception, isFlutterError);
|
|
expect('$exception', startsWith('Navigator operation requested with a context'));
|
|
});
|
|
|
|
testWidgets('Navigator.of rootNavigator finds root Navigator', (WidgetTester tester) async {
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: Column(
|
|
children: <Widget>[
|
|
const SizedBox(
|
|
height: 300.0,
|
|
child: Text('Root page'),
|
|
),
|
|
SizedBox(
|
|
height: 300.0,
|
|
child: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
if (settings.isInitialRoute) {
|
|
return MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
child: const Text('Next'),
|
|
onPressed: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
child: const Text('Inner page'),
|
|
onPressed: () {
|
|
Navigator.of(context, rootNavigator: true).push(
|
|
MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return const Text('Dialog');
|
|
}
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
);
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
));
|
|
|
|
await tester.tap(find.text('Next'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
// Both elements are on screen.
|
|
expect(tester.getTopLeft(find.text('Root page')).dy, 0.0);
|
|
expect(tester.getTopLeft(find.text('Inner page')).dy, greaterThan(300.0));
|
|
|
|
await tester.tap(find.text('Inner page'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 300));
|
|
|
|
// Dialog is pushed to the whole page and is at the top of the screen, not
|
|
// inside the inner page.
|
|
expect(tester.getTopLeft(find.text('Dialog')).dy, 0.0);
|
|
});
|
|
|
|
testWidgets('Gestures between push and build are ignored', (WidgetTester tester) async {
|
|
final List<String> log = <String>[];
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/': (BuildContext context) {
|
|
return Row(
|
|
children: <Widget>[
|
|
GestureDetector(
|
|
onTap: () {
|
|
log.add('left');
|
|
Navigator.pushNamed(context, '/second');
|
|
},
|
|
child: const Text('left'),
|
|
),
|
|
GestureDetector(
|
|
onTap: () { log.add('right'); },
|
|
child: const Text('right'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
'/second': (BuildContext context) => Container(),
|
|
};
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
expect(log, isEmpty);
|
|
await tester.tap(find.text('left'));
|
|
expect(log, equals(<String>['left']));
|
|
await tester.tap(find.text('right'));
|
|
expect(log, equals(<String>['left']));
|
|
});
|
|
|
|
testWidgets('Pending gestures are rejected', (WidgetTester tester) async {
|
|
final List<String> log = <String>[];
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/': (BuildContext context) {
|
|
return Row(
|
|
children: <Widget>[
|
|
GestureDetector(
|
|
onTap: () {
|
|
log.add('left');
|
|
Navigator.pushNamed(context, '/second');
|
|
},
|
|
child: const Text('left')
|
|
),
|
|
GestureDetector(
|
|
onTap: () { log.add('right'); },
|
|
child: const Text('right'),
|
|
),
|
|
]
|
|
);
|
|
},
|
|
'/second': (BuildContext context) => Container(),
|
|
};
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('right')), pointer: 23);
|
|
expect(log, isEmpty);
|
|
await tester.tap(find.text('left'));
|
|
expect(log, equals(<String>['left']));
|
|
await gesture.up();
|
|
expect(log, equals(<String>['left']));
|
|
|
|
// This test doesn't work because it relies on part of the pointer event
|
|
// dispatching mechanism that is mocked out in testing. We should use the real
|
|
// mechanism even during testing and enable this test.
|
|
// TODO(abarth): Test more of the real code and enable this test.
|
|
// See https://github.com/flutter/flutter/issues/4771.
|
|
}, skip: true);
|
|
|
|
testWidgets('popAndPushNamed', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.popAndPushNamed(context, '/B'); }),
|
|
'/B': (BuildContext context) => OnTapPage(id: 'B', onTap: () { Navigator.pop(context); }),
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A', skipOffstage: false), findsNothing);
|
|
expect(find.text('B', skipOffstage: false), findsNothing);
|
|
|
|
await tester.tap(find.text('/'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
await tester.tap(find.text('A'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Push and pop should trigger the observers', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }),
|
|
};
|
|
bool isPushed = false;
|
|
bool isPopped = false;
|
|
final TestObserver observer = TestObserver()
|
|
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
// Pushes the initial route.
|
|
expect(route is PageRoute && route.settings.name == '/', isTrue);
|
|
expect(previousRoute, isNull);
|
|
isPushed = true;
|
|
}
|
|
..onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
isPopped = true;
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
routes: routes,
|
|
navigatorObservers: <NavigatorObserver>[observer],
|
|
));
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(isPushed, isTrue);
|
|
expect(isPopped, isFalse);
|
|
|
|
isPushed = false;
|
|
isPopped = false;
|
|
observer.onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
expect(route is PageRoute && route.settings.name == '/A', isTrue);
|
|
expect(previousRoute is PageRoute && previousRoute.settings.name == '/', isTrue);
|
|
isPushed = true;
|
|
};
|
|
|
|
await tester.tap(find.text('/'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
expect(isPushed, isTrue);
|
|
expect(isPopped, isFalse);
|
|
|
|
isPushed = false;
|
|
isPopped = false;
|
|
observer.onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
expect(route is PageRoute && route.settings.name == '/A', isTrue);
|
|
expect(previousRoute is PageRoute && previousRoute.settings.name == '/', isTrue);
|
|
isPopped = true;
|
|
};
|
|
|
|
await tester.tap(find.text('A'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(isPushed, isFalse);
|
|
expect(isPopped, isTrue);
|
|
});
|
|
|
|
testWidgets('Add and remove an observer should work', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }),
|
|
};
|
|
bool isPushed = false;
|
|
bool isPopped = false;
|
|
final TestObserver observer1 = TestObserver();
|
|
final TestObserver observer2 = TestObserver()
|
|
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
isPushed = true;
|
|
}
|
|
..onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
isPopped = true;
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
routes: routes,
|
|
navigatorObservers: <NavigatorObserver>[observer1],
|
|
));
|
|
expect(isPushed, isFalse);
|
|
expect(isPopped, isFalse);
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
routes: routes,
|
|
navigatorObservers: <NavigatorObserver>[observer1, observer2],
|
|
));
|
|
await tester.tap(find.text('/'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(isPushed, isTrue);
|
|
expect(isPopped, isFalse);
|
|
|
|
isPushed = false;
|
|
isPopped = false;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
routes: routes,
|
|
navigatorObservers: <NavigatorObserver>[observer1],
|
|
));
|
|
await tester.tap(find.text('A'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(isPushed, isFalse);
|
|
expect(isPopped, isFalse);
|
|
});
|
|
|
|
testWidgets('replaceNamed', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushReplacementNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pushReplacementNamed(context, '/B'); }),
|
|
'/B': (BuildContext context) => const OnTapPage(id: 'B'),
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
await tester.tap(find.text('/')); // replaceNamed('/A')
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('A')); // replaceNamed('/B')
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('replaceNamed returned value', (WidgetTester tester) async {
|
|
Future<String> value;
|
|
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { value = Navigator.pushReplacementNamed(context, '/B', result: 'B'); }),
|
|
'/B': (BuildContext context) => OnTapPage(id: 'B', onTap: () { Navigator.pop(context, 'B'); }),
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return PageRouteBuilder<String>(
|
|
settings: settings,
|
|
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
|
|
return routes[settings.name](context);
|
|
},
|
|
);
|
|
}
|
|
));
|
|
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A', skipOffstage: false), findsNothing);
|
|
expect(find.text('B', skipOffstage: false), findsNothing);
|
|
|
|
await tester.tap(find.text('/')); // pushNamed('/A'), stack becomes /, /A
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
await tester.tap(find.text('A')); // replaceNamed('/B'), stack becomes /, /B
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('B')); // pop, stack becomes /
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
final String replaceNamedValue = await value; // replaceNamed result was 'B'
|
|
expect(replaceNamedValue, 'B');
|
|
});
|
|
|
|
testWidgets('removeRoute', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> pageBuilders = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pushNamed(context, '/B'); }),
|
|
'/B': (BuildContext context) => const OnTapPage(id: 'B'),
|
|
};
|
|
final Map<String, Route<String>> routes = <String, Route<String>>{};
|
|
|
|
Route<String> removedRoute;
|
|
Route<String> previousRoute;
|
|
|
|
final TestObserver observer = TestObserver()
|
|
..onRemoved = (Route<dynamic> route, Route<dynamic> previous) {
|
|
removedRoute = route as Route<String>;
|
|
previousRoute = previous as Route<String>;
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorObservers: <NavigatorObserver>[observer],
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
routes[settings.name] = PageRouteBuilder<String>(
|
|
settings: settings,
|
|
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
|
|
return pageBuilders[settings.name](context);
|
|
},
|
|
);
|
|
return routes[settings.name];
|
|
},
|
|
));
|
|
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
await tester.tap(find.text('/')); // pushNamed('/A'), stack becomes /, /A
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
await tester.tap(find.text('A')); // pushNamed('/B'), stack becomes /, /A, /B
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsOneWidget);
|
|
|
|
// Verify that the navigator's stack is ordered as expected.
|
|
expect(routes['/'].isActive, true);
|
|
expect(routes['/A'].isActive, true);
|
|
expect(routes['/B'].isActive, true);
|
|
expect(routes['/'].isFirst, true);
|
|
expect(routes['/B'].isCurrent, true);
|
|
|
|
final NavigatorState navigator = tester.state<NavigatorState>(find.byType(Navigator));
|
|
navigator.removeRoute(routes['/B']); // stack becomes /, /A
|
|
await tester.pump();
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
// Verify that the navigator's stack no longer includes /B
|
|
expect(routes['/'].isActive, true);
|
|
expect(routes['/A'].isActive, true);
|
|
expect(routes['/B'].isActive, false);
|
|
expect(routes['/'].isFirst, true);
|
|
expect(routes['/A'].isCurrent, true);
|
|
|
|
expect(removedRoute, routes['/B']);
|
|
expect(previousRoute, routes['/A']);
|
|
|
|
navigator.removeRoute(routes['/A']); // stack becomes just /
|
|
await tester.pump();
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(find.text('A'), findsNothing);
|
|
expect(find.text('B'), findsNothing);
|
|
|
|
// Verify that the navigator's stack no longer includes /A
|
|
expect(routes['/'].isActive, true);
|
|
expect(routes['/A'].isActive, false);
|
|
expect(routes['/B'].isActive, false);
|
|
expect(routes['/'].isFirst, true);
|
|
expect(routes['/'].isCurrent, true);
|
|
expect(removedRoute, routes['/A']);
|
|
expect(previousRoute, routes['/']);
|
|
});
|
|
|
|
testWidgets('remove a route whose value is awaited', (WidgetTester tester) async {
|
|
Future<String> pageValue;
|
|
final Map<String, WidgetBuilder> pageBuilders = <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => OnTapPage(id: '/', onTap: () { pageValue = Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context, 'A'); }),
|
|
};
|
|
final Map<String, Route<String>> routes = <String, Route<String>>{};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
routes[settings.name] = PageRouteBuilder<String>(
|
|
settings: settings,
|
|
pageBuilder: (BuildContext context, Animation<double> _, Animation<double> __) {
|
|
return pageBuilders[settings.name](context);
|
|
},
|
|
);
|
|
return routes[settings.name];
|
|
}
|
|
));
|
|
|
|
await tester.tap(find.text('/')); // pushNamed('/A'), stack becomes /, /A
|
|
await tester.pumpAndSettle();
|
|
pageValue.then((String value) { assert(false); });
|
|
|
|
final NavigatorState navigator = tester.state<NavigatorState>(find.byType(Navigator));
|
|
navigator.removeRoute(routes['/A']); // stack becomes /, pageValue will not complete
|
|
});
|
|
|
|
testWidgets('replacing route can be observed', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
|
|
final List<String> log = <String>[];
|
|
final TestObserver observer = TestObserver()
|
|
..onPushed = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
log.add('pushed ${route.settings.name} (previous is ${previousRoute == null ? "<none>" : previousRoute.settings.name})');
|
|
}
|
|
..onPopped = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
log.add('popped ${route.settings.name} (previous is ${previousRoute == null ? "<none>" : previousRoute.settings.name})');
|
|
}
|
|
..onRemoved = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
log.add('removed ${route.settings.name} (previous is ${previousRoute == null ? "<none>" : previousRoute.settings.name})');
|
|
}
|
|
..onReplaced = (Route<dynamic> newRoute, Route<dynamic> oldRoute) {
|
|
log.add('replaced ${oldRoute.settings.name} with ${newRoute.settings.name}');
|
|
};
|
|
Route<void> routeB;
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorKey: key,
|
|
navigatorObservers: <NavigatorObserver>[observer],
|
|
home: FlatButton(
|
|
child: const Text('A'),
|
|
onPressed: () {
|
|
key.currentState.push<void>(routeB = MaterialPageRoute<void>(
|
|
settings: const RouteSettings(name: 'B'),
|
|
builder: (BuildContext context) {
|
|
return FlatButton(
|
|
child: const Text('B'),
|
|
onPressed: () {
|
|
key.currentState.push<void>(MaterialPageRoute<int>(
|
|
settings: const RouteSettings(name: 'C'),
|
|
builder: (BuildContext context) {
|
|
return FlatButton(
|
|
child: const Text('C'),
|
|
onPressed: () {
|
|
key.currentState.replace(
|
|
oldRoute: routeB,
|
|
newRoute: MaterialPageRoute<int>(
|
|
settings: const RouteSettings(name: 'D'),
|
|
builder: (BuildContext context) {
|
|
return const Text('D');
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
));
|
|
},
|
|
);
|
|
},
|
|
));
|
|
},
|
|
),
|
|
));
|
|
expect(log, <String>['pushed / (previous is <none>)']);
|
|
await tester.tap(find.text('A'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(log, <String>['pushed / (previous is <none>)', 'pushed B (previous is /)']);
|
|
await tester.tap(find.text('B'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(log, <String>['pushed / (previous is <none>)', 'pushed B (previous is /)', 'pushed C (previous is B)']);
|
|
await tester.tap(find.text('C'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(log, <String>['pushed / (previous is <none>)', 'pushed B (previous is /)', 'pushed C (previous is B)', 'replaced B with D']);
|
|
});
|
|
|
|
testWidgets('didStartUserGesture observable', (WidgetTester tester) async {
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/' : (BuildContext context) => OnTapPage(id: '/', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: 'A', onTap: () { Navigator.pop(context); }),
|
|
};
|
|
|
|
Route<dynamic> observedRoute;
|
|
Route<dynamic> observedPreviousRoute;
|
|
final TestObserver observer = TestObserver()
|
|
..onStartUserGesture = (Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
observedRoute = route;
|
|
observedPreviousRoute = previousRoute;
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
routes: routes,
|
|
navigatorObservers: <NavigatorObserver>[observer],
|
|
));
|
|
|
|
await tester.tap(find.text('/'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('A'), findsOneWidget);
|
|
|
|
tester.state<NavigatorState>(find.byType(Navigator)).didStartUserGesture();
|
|
|
|
expect(observedRoute.settings.name, '/A');
|
|
expect(observedPreviousRoute.settings.name, '/');
|
|
});
|
|
|
|
testWidgets('ModalRoute.of sets up a route to rebuild if its state changes', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> key = GlobalKey<NavigatorState>();
|
|
final List<String> log = <String>[];
|
|
Route<void> routeB;
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorKey: key,
|
|
home: FlatButton(
|
|
child: const Text('A'),
|
|
onPressed: () {
|
|
key.currentState.push<void>(routeB = MaterialPageRoute<void>(
|
|
settings: const RouteSettings(name: 'B'),
|
|
builder: (BuildContext context) {
|
|
log.add('building B');
|
|
return FlatButton(
|
|
child: const Text('B'),
|
|
onPressed: () {
|
|
key.currentState.push<void>(MaterialPageRoute<int>(
|
|
settings: const RouteSettings(name: 'C'),
|
|
builder: (BuildContext context) {
|
|
log.add('building C');
|
|
log.add('found ${ModalRoute.of(context).settings.name}');
|
|
return FlatButton(
|
|
child: const Text('C'),
|
|
onPressed: () {
|
|
key.currentState.replace(
|
|
oldRoute: routeB,
|
|
newRoute: MaterialPageRoute<int>(
|
|
settings: const RouteSettings(name: 'D'),
|
|
builder: (BuildContext context) {
|
|
log.add('building D');
|
|
return const Text('D');
|
|
},
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
));
|
|
},
|
|
);
|
|
},
|
|
));
|
|
},
|
|
),
|
|
));
|
|
expect(log, <String>[]);
|
|
await tester.tap(find.text('A'));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 10));
|
|
expect(log, <String>['building B']);
|
|
await tester.tap(find.text('B'));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 10));
|
|
expect(log, <String>['building B', 'building C', 'found C']);
|
|
await tester.tap(find.text('C'));
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 10));
|
|
expect(log, <String>['building B', 'building C', 'found C', 'building D']);
|
|
key.currentState.pop<void>();
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 10));
|
|
expect(log, <String>['building B', 'building C', 'found C', 'building D', 'building C', 'found C']);
|
|
});
|
|
|
|
testWidgets('route semantics', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => OnTapPage(id: '1', onTap: () { Navigator.pushNamed(context, '/A'); }),
|
|
'/A': (BuildContext context) => OnTapPage(id: '2', onTap: () { Navigator.pushNamed(context, '/B/C'); }),
|
|
'/B/C': (BuildContext context) => const OnTapPage(id: '3'),
|
|
};
|
|
|
|
await tester.pumpWidget(MaterialApp(routes: routes));
|
|
|
|
expect(semantics, includesNodeWith(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
));
|
|
expect(semantics, includesNodeWith(
|
|
label: 'Page 1',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.namesRoute,
|
|
SemanticsFlag.isHeader,
|
|
],
|
|
));
|
|
|
|
await tester.tap(find.text('1')); // pushNamed('/A')
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
expect(semantics, includesNodeWith(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
));
|
|
expect(semantics, includesNodeWith(
|
|
label: 'Page 2',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.namesRoute,
|
|
SemanticsFlag.isHeader,
|
|
],
|
|
));
|
|
|
|
await tester.tap(find.text('2')); // pushNamed('/B/C')
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
expect(semantics, includesNodeWith(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.scopesRoute,
|
|
],
|
|
));
|
|
expect(semantics, includesNodeWith(
|
|
label: 'Page 3',
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.namesRoute,
|
|
SemanticsFlag.isHeader,
|
|
],
|
|
));
|
|
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('arguments for named routes on Navigator', (WidgetTester tester) async {
|
|
GlobalKey currentRouteKey;
|
|
final List<Object> arguments = <Object>[];
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
arguments.add(settings.arguments);
|
|
return MaterialPageRoute<void>(
|
|
settings: settings,
|
|
builder: (BuildContext context) => Center(key: currentRouteKey = GlobalKey(), child: Text(settings.name)),
|
|
);
|
|
},
|
|
));
|
|
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(arguments.single, isNull);
|
|
arguments.clear();
|
|
|
|
Navigator.pushNamed(
|
|
currentRouteKey.currentContext,
|
|
'/A',
|
|
arguments: 'pushNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsOneWidget);
|
|
expect(arguments.single, 'pushNamed');
|
|
arguments.clear();
|
|
|
|
Navigator.popAndPushNamed(
|
|
currentRouteKey.currentContext,
|
|
'/B',
|
|
arguments: 'popAndPushNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsOneWidget);
|
|
expect(arguments.single, 'popAndPushNamed');
|
|
arguments.clear();
|
|
|
|
Navigator.pushNamedAndRemoveUntil(
|
|
currentRouteKey.currentContext,
|
|
'/C',
|
|
(Route<dynamic> route) => route.isFirst,
|
|
arguments: 'pushNamedAndRemoveUntil',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsNothing);
|
|
expect(find.text('/C'), findsOneWidget);
|
|
expect(arguments.single, 'pushNamedAndRemoveUntil');
|
|
arguments.clear();
|
|
|
|
Navigator.pushReplacementNamed(
|
|
currentRouteKey.currentContext,
|
|
'/D',
|
|
arguments: 'pushReplacementNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsNothing);
|
|
expect(find.text('/C'), findsNothing);
|
|
expect(find.text('/D'), findsOneWidget);
|
|
expect(arguments.single, 'pushReplacementNamed');
|
|
arguments.clear();
|
|
});
|
|
|
|
testWidgets('arguments for named routes on NavigatorState', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
final List<Object> arguments = <Object>[];
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorKey: navigatorKey,
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
arguments.add(settings.arguments);
|
|
return MaterialPageRoute<void>(
|
|
settings: settings,
|
|
builder: (BuildContext context) => Center(child: Text(settings.name)),
|
|
);
|
|
},
|
|
));
|
|
|
|
expect(find.text('/'), findsOneWidget);
|
|
expect(arguments.single, isNull);
|
|
arguments.clear();
|
|
|
|
navigatorKey.currentState.pushNamed(
|
|
'/A',
|
|
arguments:'pushNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsOneWidget);
|
|
expect(arguments.single, 'pushNamed');
|
|
arguments.clear();
|
|
|
|
navigatorKey.currentState.popAndPushNamed(
|
|
'/B',
|
|
arguments: 'popAndPushNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsOneWidget);
|
|
expect(arguments.single, 'popAndPushNamed');
|
|
arguments.clear();
|
|
|
|
navigatorKey.currentState.pushNamedAndRemoveUntil(
|
|
'/C',
|
|
(Route<dynamic> route) => route.isFirst,
|
|
arguments: 'pushNamedAndRemoveUntil',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsNothing);
|
|
expect(find.text('/C'), findsOneWidget);
|
|
expect(arguments.single, 'pushNamedAndRemoveUntil');
|
|
arguments.clear();
|
|
|
|
navigatorKey.currentState.pushReplacementNamed(
|
|
'/D',
|
|
arguments: 'pushReplacementNamed',
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('/'), findsNothing);
|
|
expect(find.text('/A'), findsNothing);
|
|
expect(find.text('/B'), findsNothing);
|
|
expect(find.text('/C'), findsNothing);
|
|
expect(find.text('/D'), findsOneWidget);
|
|
expect(arguments.single, 'pushReplacementNamed');
|
|
arguments.clear();
|
|
});
|
|
|
|
testWidgets('Initial route can have gaps', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> keyNav = GlobalKey<NavigatorState>();
|
|
const Key keyRoot = Key('Root');
|
|
const Key keyA = Key('A');
|
|
const Key keyABC = Key('ABC');
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorKey: keyNav,
|
|
initialRoute: '/A/B/C',
|
|
routes: <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => Container(key: keyRoot),
|
|
'/A': (BuildContext context) => Container(key: keyA),
|
|
// The route /A/B is intentionally left out.
|
|
'/A/B/C': (BuildContext context) => Container(key: keyABC),
|
|
},
|
|
),
|
|
);
|
|
|
|
// The initial route /A/B/C should've been pushed successfully.
|
|
expect(find.byKey(keyRoot, skipOffstage: false), findsOneWidget);
|
|
expect(find.byKey(keyA, skipOffstage: false), findsOneWidget);
|
|
expect(find.byKey(keyABC), findsOneWidget);
|
|
|
|
keyNav.currentState.pop();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byKey(keyRoot, skipOffstage: false), findsOneWidget);
|
|
expect(find.byKey(keyA), findsOneWidget);
|
|
expect(find.byKey(keyABC, skipOffstage: false), findsNothing);
|
|
});
|
|
|
|
testWidgets('The full initial route has to be matched', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> keyNav = GlobalKey<NavigatorState>();
|
|
const Key keyRoot = Key('Root');
|
|
const Key keyA = Key('A');
|
|
const Key keyAB = Key('AB');
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorKey: keyNav,
|
|
initialRoute: '/A/B/C',
|
|
routes: <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => Container(key: keyRoot),
|
|
'/A': (BuildContext context) => Container(key: keyA),
|
|
'/A/B': (BuildContext context) => Container(key: keyAB),
|
|
// The route /A/B/C is intentionally left out.
|
|
},
|
|
),
|
|
);
|
|
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isA<String>());
|
|
expect(exception.startsWith('Could not navigate to initial route.'), isTrue);
|
|
|
|
// Only the root route should've been pushed.
|
|
expect(find.byKey(keyRoot), findsOneWidget);
|
|
expect(find.byKey(keyA), findsNothing);
|
|
expect(find.byKey(keyAB), findsNothing);
|
|
});
|
|
|
|
group('error control test', () {
|
|
testWidgets('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(Navigator(
|
|
key: navigatorKey,
|
|
onGenerateRoute: (_) => null,
|
|
));
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isNotNull);
|
|
expect(exception, isFlutterError);
|
|
final FlutterError error = exception as FlutterError;
|
|
expect(error, isNotNull);
|
|
expect(error.diagnostics.last, isA<DiagnosticsProperty<NavigatorState>>());
|
|
expect(
|
|
error.toStringDeep(),
|
|
equalsIgnoringHashCodes(
|
|
'FlutterError\n'
|
|
' If a Navigator has no onUnknownRoute, then its onGenerateRoute\n'
|
|
' must never return null.\n'
|
|
' When trying to build the route "/", onGenerateRoute returned\n'
|
|
' null, but there was no onUnknownRoute callback specified.\n'
|
|
' The Navigator was:\n'
|
|
' NavigatorState#4d6bf(lifecycle state: created)\n',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('onUnknownRoute null and onGenerateRoute returns null', (WidgetTester tester) async {
|
|
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(Navigator(
|
|
key: navigatorKey,
|
|
onGenerateRoute: (_) => null,
|
|
onUnknownRoute: (_) => null,
|
|
));
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isNotNull);
|
|
expect(exception, isFlutterError);
|
|
final FlutterError error = exception as FlutterError;
|
|
expect(error, isNotNull);
|
|
expect(error.diagnostics.last, isA<DiagnosticsProperty<NavigatorState>>());
|
|
expect(
|
|
error.toStringDeep(),
|
|
equalsIgnoringHashCodes(
|
|
'FlutterError\n'
|
|
' A Navigator\'s onUnknownRoute returned null.\n'
|
|
' When trying to build the route "/", both onGenerateRoute and\n'
|
|
' onUnknownRoute returned null. The onUnknownRoute callback should\n'
|
|
' never return null.\n'
|
|
' The Navigator was:\n'
|
|
' NavigatorState#38036(lifecycle state: created)\n',
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
testWidgets('OverlayEntry of topmost initial route is marked as opaque', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/38038.
|
|
|
|
final Key root = UniqueKey();
|
|
final Key intermediate = UniqueKey();
|
|
final GlobalKey topmost = GlobalKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
initialRoute: '/A/B',
|
|
routes: <String, WidgetBuilder>{
|
|
'/': (BuildContext context) => Container(key: root),
|
|
'/A': (BuildContext context) => Container(key: intermediate),
|
|
'/A/B': (BuildContext context) => Container(key: topmost),
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(ModalRoute.of(topmost.currentContext).overlayEntries.first.opaque, isTrue);
|
|
|
|
expect(find.byKey(root), findsNothing); // hidden by opaque Route
|
|
expect(find.byKey(intermediate), findsNothing); // hidden by opaque Route
|
|
expect(find.byKey(topmost), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('OverlayEntry of topmost route is set to opaque after Push', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/38038.
|
|
|
|
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorKey: navigator,
|
|
initialRoute: '/',
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return NoAnimationPageRoute(
|
|
pageBuilder: (_) => Container(key: ValueKey<String>(settings.name)),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
expect(find.byKey(const ValueKey<String>('/')), findsOneWidget);
|
|
|
|
navigator.currentState.pushNamed('/A');
|
|
await tester.pump();
|
|
|
|
final BuildContext topMostContext = tester.element(find.byKey(const ValueKey<String>('/A')));
|
|
expect(ModalRoute.of(topMostContext).overlayEntries.first.opaque, isTrue);
|
|
|
|
expect(find.byKey(const ValueKey<String>('/')), findsNothing); // hidden by /A
|
|
expect(find.byKey(const ValueKey<String>('/A')), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('OverlayEntry of topmost route is set to opaque after Replace', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/38038.
|
|
|
|
final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
navigatorKey: navigator,
|
|
initialRoute: '/A/B',
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return NoAnimationPageRoute(
|
|
pageBuilder: (_) => Container(key: ValueKey<String>(settings.name)),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
expect(find.byKey(const ValueKey<String>('/')), findsNothing);
|
|
expect(find.byKey(const ValueKey<String>('/A')), findsNothing);
|
|
expect(find.byKey(const ValueKey<String>('/A/B')), findsOneWidget);
|
|
|
|
final Route<dynamic> oldRoute = ModalRoute.of(
|
|
tester.element(find.byKey(const ValueKey<String>('/A'), skipOffstage: false)),
|
|
);
|
|
final Route<void> newRoute = NoAnimationPageRoute(
|
|
pageBuilder: (_) => Container(key: const ValueKey<String>('/C')),
|
|
);
|
|
|
|
navigator.currentState.replace<void>(oldRoute: oldRoute, newRoute: newRoute);
|
|
await tester.pump();
|
|
|
|
expect(newRoute.overlayEntries.first.opaque, isTrue);
|
|
|
|
expect(find.byKey(const ValueKey<String>('/')), findsNothing); // hidden by /A/B
|
|
expect(find.byKey(const ValueKey<String>('/A')), findsNothing); // replaced
|
|
expect(find.byKey(const ValueKey<String>('/C')), findsNothing); // hidden by /A/B
|
|
expect(find.byKey(const ValueKey<String>('/A/B')), findsOneWidget);
|
|
|
|
navigator.currentState.pop();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.byKey(const ValueKey<String>('/')), findsNothing); // hidden by /C
|
|
expect(find.byKey(const ValueKey<String>('/A')), findsNothing); // replaced
|
|
expect(find.byKey(const ValueKey<String>('/A/B')), findsNothing); // popped
|
|
expect(find.byKey(const ValueKey<String>('/C')), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
class NoAnimationPageRoute extends PageRouteBuilder<void> {
|
|
NoAnimationPageRoute({WidgetBuilder pageBuilder})
|
|
: super(pageBuilder: (BuildContext context, __, ___) {
|
|
return pageBuilder(context);
|
|
});
|
|
|
|
@override
|
|
AnimationController createAnimationController() {
|
|
return super.createAnimationController()..value = 1.0;
|
|
}
|
|
}
|