| // 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)); |
| }); |
| }); |
| } |