blob: d43df8a88cff7e5afb510d8f91a4898322760cba [file] [log] [blame]
// 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 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'observer_tester.dart';
void main() {
testWidgets('Back during pushReplacement', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: const Material(child: Text('home')),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => const Material(child: Text('a')),
'/b': (BuildContext context) => const Material(child: Text('b')),
},
));
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pushNamed('/a');
await tester.pumpAndSettle();
expect(find.text('a'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.pushReplacementNamed('/b');
await tester.pump();
await tester.pump(const Duration(milliseconds: 10));
expect(find.text('a'), findsOneWidget);
expect(find.text('b'), findsOneWidget);
expect(find.text('home'), findsNothing);
navigator.pop();
await tester.pumpAndSettle();
expect(find.text('a'), findsNothing);
expect(find.text('b'), findsNothing);
expect(find.text('home'), findsOneWidget);
});
group('pushAndRemoveUntil', () {
testWidgets('notifies appropriately', (WidgetTester tester) async {
final TestObserver observer = TestObserver();
final Widget myApp = MaterialApp(
home: const Material(child: Text('home')),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => const Material(child: Text('a')),
'/b': (BuildContext context) => const Material(child: Text('b')),
},
navigatorObservers: <NavigatorObserver>[observer],
);
await tester.pumpWidget(myApp);
final NavigatorState navigator = tester.state(find.byType(Navigator));
final List<String> log = <String>[];
observer
..onPushed = (Route<dynamic>? route, Route<dynamic>? previousRoute) {
log.add('${route!.settings.name} pushed, previous route: ${previousRoute!.settings.name}');
}
..onRemoved = (Route<dynamic>? route, Route<dynamic>? previousRoute) {
log.add('${route!.settings.name} removed, previous route: ${previousRoute?.settings.name}');
};
navigator.pushNamed('/a');
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsOneWidget);
expect(find.text('a', skipOffstage: false), findsOneWidget);
expect(find.text('b', skipOffstage: false), findsNothing);
// Remove all routes below
navigator.pushNamedAndRemoveUntil('/b', (Route<dynamic> route) => false);
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsNothing);
expect(find.text('a', skipOffstage: false), findsNothing);
expect(find.text('b', skipOffstage: false), findsOneWidget);
expect(log, equals(<String>[
'/a pushed, previous route: /',
'/b pushed, previous route: /a',
'/a removed, previous route: null',
'/ removed, previous route: null',
]));
log.clear();
navigator.pushNamed('/');
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsOneWidget);
expect(find.text('a', skipOffstage: false), findsNothing);
expect(find.text('b', skipOffstage: false), findsOneWidget);
// Remove only some routes below
navigator.pushNamedAndRemoveUntil('/a', ModalRoute.withName('/b'));
await tester.pumpAndSettle();
expect(find.text('home', skipOffstage: false), findsNothing);
expect(find.text('a', skipOffstage: false), findsOneWidget);
expect(find.text('b', skipOffstage: false), findsOneWidget);
expect(log, equals(<String>[
'/ pushed, previous route: /b',
'/a pushed, previous route: /',
'/ removed, previous route: /b',
]));
});
testWidgets('triggers page transition animation for pushed route', (WidgetTester tester) async {
final Widget myApp = MaterialApp(
home: const Material(child: Text('home')),
routes: <String, WidgetBuilder>{
'/a': (BuildContext context) => const Material(child: Text('a')),
'/b': (BuildContext context) => const Material(child: Text('b')),
},
);
await tester.pumpWidget(myApp);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pushNamed('/a');
await tester.pumpAndSettle();
navigator.pushNamedAndRemoveUntil('/b', (Route<dynamic> route) => false);
await tester.pump();
await tester.pump(const Duration(milliseconds: 100));
// We are mid-transition, both pages are onstage
expect(find.text('a'), findsOneWidget);
expect(find.text('b'), findsOneWidget);
// Complete transition
await tester.pumpAndSettle();
expect(find.text('a'), findsNothing);
expect(find.text('b'), findsOneWidget);
});
testWidgets('Hero transition triggers when preceding route contains hero, and predicate route does not', (WidgetTester tester) async {
const String kHeroTag = 'hero';
final Widget myApp = MaterialApp(
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const Material(child: Text('home')),
'/a': (BuildContext context) => const Material(child: Hero(
tag: kHeroTag,
child: Text('a'),
)),
'/b': (BuildContext context) => const Material(child: Padding(
padding: EdgeInsets.all(100.0),
child: Hero(
tag: kHeroTag,
child: Text('b'),
),
)),
},
);
await tester.pumpWidget(myApp);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pushNamed('/a');
await tester.pumpAndSettle();
navigator.pushNamedAndRemoveUntil('/b', ModalRoute.withName('/'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 16));
expect(find.text('b'), isOnstage);
// 'b' text is heroing to its new location
final Offset bOffset = tester.getTopLeft(find.text('b'));
expect(bOffset.dx, greaterThan(0.0));
expect(bOffset.dx, lessThan(100.0));
expect(bOffset.dy, greaterThan(0.0));
expect(bOffset.dy, lessThan(100.0));
await tester.pump(const Duration(seconds: 1));
expect(find.text('a'), findsNothing);
expect(find.text('b'), isOnstage);
});
testWidgets('Hero transition does not trigger when preceding route does not contain hero, but predicate route does', (WidgetTester tester) async {
const String kHeroTag = 'hero';
final Widget myApp = MaterialApp(
theme: ThemeData(
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
},
),
),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => const Material(child: Hero(
tag:kHeroTag,
child: Text('home'),
)),
'/a': (BuildContext context) => const Material(child: Text('a')),
'/b': (BuildContext context) => const Material(child: Padding(
padding: EdgeInsets.all(100.0),
child: Hero(
tag: kHeroTag,
child: Text('b'),
),
)),
},
);
await tester.pumpWidget(myApp);
final NavigatorState navigator = tester.state(find.byType(Navigator));
navigator.pushNamed('/a');
await tester.pumpAndSettle();
navigator.pushNamedAndRemoveUntil('/b', ModalRoute.withName('/'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 16));
expect(find.text('b'), isOnstage);
// 'b' text is sliding in from the right, no hero transition
final Offset bOffset = tester.getTopLeft(find.text('b'));
expect(bOffset.dx, 100.0);
expect(bOffset.dy, greaterThan(100.0));
});
});
}