| // 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:collection'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:mockito/mockito.dart'; |
| |
| final List<String> results = <String>[]; |
| |
| Set<TestRoute> routes = HashSet<TestRoute>(); |
| |
| class TestRoute extends Route<String> with LocalHistoryRoute<String> { |
| TestRoute(this.name); |
| final String name; |
| |
| @override |
| List<OverlayEntry> get overlayEntries => _entries; |
| |
| final List<OverlayEntry> _entries = <OverlayEntry>[]; |
| |
| void log(String s) { |
| results.add('$name: $s'); |
| } |
| |
| @override |
| void install() { |
| log('install'); |
| final OverlayEntry entry = OverlayEntry( |
| builder: (BuildContext context) => Container(), |
| opaque: true, |
| ); |
| _entries.add(entry); |
| routes.add(this); |
| super.install(); |
| } |
| |
| @override |
| TickerFuture didPush() { |
| log('didPush'); |
| return super.didPush(); |
| } |
| |
| @override |
| void didAdd() { |
| log('didAdd'); |
| super.didAdd(); |
| } |
| |
| @override |
| void didReplace(Route<dynamic> oldRoute) { |
| expect(oldRoute, isA<TestRoute>()); |
| final TestRoute castRoute = oldRoute as TestRoute; |
| log('didReplace ${castRoute.name}'); |
| super.didReplace(castRoute); |
| } |
| |
| @override |
| bool didPop(String result) { |
| log('didPop $result'); |
| bool returnValue; |
| if (returnValue = super.didPop(result)) |
| navigator.finalizeRoute(this); |
| return returnValue; |
| } |
| |
| @override |
| void didPopNext(Route<dynamic> nextRoute) { |
| expect(nextRoute, isA<TestRoute>()); |
| final TestRoute castRoute = nextRoute as TestRoute; |
| log('didPopNext ${castRoute.name}'); |
| super.didPopNext(castRoute); |
| } |
| |
| @override |
| void didChangeNext(Route<dynamic> nextRoute) { |
| expect(nextRoute, anyOf(isNull, isA<TestRoute>())); |
| final TestRoute castRoute = nextRoute as TestRoute; |
| log('didChangeNext ${castRoute?.name}'); |
| super.didChangeNext(castRoute); |
| } |
| |
| @override |
| void dispose() { |
| log('dispose'); |
| _entries.clear(); |
| routes.remove(this); |
| super.dispose(); |
| } |
| |
| } |
| |
| Future<void> 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 settings', (WidgetTester tester) async { |
| const RouteSettings settings = RouteSettings(name: 'A'); |
| expect(settings, hasOneLineDescription); |
| final RouteSettings settings2 = settings.copyWith(name: 'B'); |
| expect(settings2.name, 'B'); |
| }); |
| |
| testWidgets('Route settings arguments', (WidgetTester tester) async { |
| const RouteSettings settings = RouteSettings(name: 'A'); |
| expect(settings.arguments, isNull); |
| |
| final Object arguments = Object(); |
| final RouteSettings settings2 = RouteSettings(name: 'A', arguments: arguments); |
| expect(settings2.arguments, same(arguments)); |
| |
| final RouteSettings settings3 = settings2.copyWith(); |
| expect(settings3.arguments, equals(arguments)); |
| |
| final Object arguments2 = Object(); |
| final RouteSettings settings4 = settings2.copyWith(arguments: arguments2); |
| expect(settings4.arguments, same(arguments2)); |
| expect(settings4.arguments, isNot(same(arguments))); |
| }); |
| |
| testWidgets('Route management - push, replace, pop sequence', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('initial'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'initial: install', |
| 'initial: didAdd', |
| 'initial: didChangeNext null', |
| ], |
| ); |
| TestRoute second; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(second = TestRoute('second')); }, |
| <String>[ // stack is: initial, second |
| 'second: install', |
| 'second: didPush', |
| 'second: didChangeNext null', |
| 'initial: didChangeNext second', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('third')); }, |
| <String>[ // stack is: initial, second, third |
| 'third: install', |
| 'third: didPush', |
| 'third: didChangeNext null', |
| 'second: didChangeNext third', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.replace(oldRoute: second, newRoute: TestRoute('two')); }, |
| <String>[ // stack is: initial, two, third |
| 'two: install', |
| 'two: didReplace second', |
| 'two: didChangeNext third', |
| 'initial: didChangeNext two', |
| 'second: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('hello'); }, |
| <String>[ // stack is: initial, two |
| 'third: didPop hello', |
| 'two: didPopNext third', |
| 'third: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('good bye'); }, |
| <String>[ // stack is: initial |
| 'two: didPop good bye', |
| 'initial: didPopNext two', |
| 'two: dispose', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['initial: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route management - push, remove, pop', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('first'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'first: install', |
| 'first: didAdd', |
| 'first: didChangeNext null', |
| ], |
| ); |
| TestRoute second; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(second = TestRoute('second')); }, |
| <String>[ |
| 'second: install', |
| 'second: didPush', |
| 'second: didChangeNext null', |
| 'first: didChangeNext second', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(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', |
| 'second: didPopNext third', |
| 'third: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('three')); }, |
| <String>[ |
| 'three: install', |
| 'three: didPush', |
| 'three: didChangeNext null', |
| 'second: didChangeNext three', |
| ], |
| ); |
| TestRoute four; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(four = 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', |
| 'second: didPopNext four', |
| 'four: dispose', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['second: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route management - push, replace, popUntil', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('A'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'A: install', |
| 'A: didAdd', |
| 'A: didChangeNext null', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('B')); }, |
| <String>[ |
| 'B: install', |
| 'B: didPush', |
| 'B: didChangeNext null', |
| 'A: didChangeNext B', |
| ], |
| ); |
| TestRoute routeC; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(routeC = TestRoute('C')); }, |
| <String>[ |
| 'C: install', |
| 'C: didPush', |
| 'C: didChangeNext null', |
| 'B: didChangeNext C', |
| ], |
| ); |
| expect(routeC.isActive, isTrue); |
| TestRoute routeB; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.replaceRouteBelow(anchorRoute: routeC, newRoute: routeB = 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', |
| 'b: didPopNext C', |
| 'C: dispose', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['A: dispose', 'b: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route localHistory - popUntil', (WidgetTester tester) async { |
| final TestRoute routeA = TestRoute('A'); |
| routeA.addLocalHistoryEntry(LocalHistoryEntry( |
| onRemove: () { routeA.log('onRemove 0'); } |
| )); |
| routeA.addLocalHistoryEntry(LocalHistoryEntry( |
| onRemove: () { routeA.log('onRemove 1'); } |
| )); |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => routeA, |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); }, |
| <String>[ |
| 'A: install', |
| 'A: didAdd', |
| '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>[ |
| ], |
| ); |
| }); |
| |
| group('PageRouteObserver', () { |
| test('calls correct listeners', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware1 = MockRouteAware(); |
| final MockPageRoute route1 = MockPageRoute(); |
| observer.subscribe(pageRouteAware1, route1); |
| verify(pageRouteAware1.didPush()).called(1); |
| |
| final RouteAware pageRouteAware2 = MockRouteAware(); |
| final MockPageRoute route2 = MockPageRoute(); |
| observer.didPush(route2, route1); |
| verify(pageRouteAware1.didPushNext()).called(1); |
| |
| observer.subscribe(pageRouteAware2, route2); |
| verify(pageRouteAware2.didPush()).called(1); |
| |
| observer.didPop(route2, route1); |
| verify(pageRouteAware2.didPop()).called(1); |
| verify(pageRouteAware1.didPopNext()).called(1); |
| }); |
| |
| test('does not call listeners for non-PageRoute', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| final MockRoute route = MockRoute(); |
| observer.subscribe(pageRouteAware, pageRoute); |
| verify(pageRouteAware.didPush()); |
| |
| observer.didPush(route, pageRoute); |
| observer.didPop(route, pageRoute); |
| verifyNoMoreInteractions(pageRouteAware); |
| }); |
| |
| test('does not call listeners when already subscribed', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| observer.subscribe(pageRouteAware, pageRoute); |
| observer.subscribe(pageRouteAware, pageRoute); |
| verify(pageRouteAware.didPush()).called(1); |
| }); |
| |
| test('does not call listeners when unsubscribed', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| final MockPageRoute nextPageRoute = MockPageRoute(); |
| observer.subscribe(pageRouteAware, pageRoute); |
| observer.subscribe(pageRouteAware, nextPageRoute); |
| verify(pageRouteAware.didPush()).called(2); |
| |
| observer.unsubscribe(pageRouteAware); |
| |
| observer.didPush(nextPageRoute, pageRoute); |
| observer.didPop(nextPageRoute, pageRoute); |
| verifyNoMoreInteractions(pageRouteAware); |
| }); |
| }); |
| |
| testWidgets('Can autofocus a TextField nested in a Focus in a route.', (WidgetTester tester) async { |
| final TextEditingController controller = TextEditingController(); |
| |
| final FocusNode focusNode = FocusNode(debugLabel: 'Test Node'); |
| await tester.pumpWidget( |
| Material( |
| child: MaterialApp( |
| onGenerateRoute: (RouteSettings settings) { |
| return PageRouteBuilder<void>( |
| settings: settings, |
| pageBuilder: (BuildContext context, Animation<double> input, Animation<double> out) { |
| return Focus( |
| child: TextField( |
| autofocus: true, |
| focusNode: focusNode, |
| controller: controller, |
| ), |
| ); |
| }, |
| ); |
| }, |
| ), |
| ), |
| ); |
| await tester.pump(); |
| |
| expect(focusNode.hasPrimaryFocus, isTrue); |
| }); |
| |
| group('TransitionRoute', () { |
| testWidgets('secondary animation is kDismissed when next route finishes pop', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Text('home'), |
| ) |
| ); |
| |
| // Push page one, its secondary animation is kAlwaysDismissedAnimation. |
| ProxyAnimation secondaryAnimationProxyPageOne; |
| ProxyAnimation animationPageOne; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageOne = secondaryAnimation as ProxyAnimation; |
| animationPageOne = animation as ProxyAnimation; |
| return const Text('Page One'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageOne = secondaryAnimationProxyPageOne.parent as ProxyAnimation; |
| expect(animationPageOne.value, 1.0); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| |
| // Push page two, the secondary animation of page one is the primary |
| // animation of page two. |
| ProxyAnimation secondaryAnimationProxyPageTwo; |
| ProxyAnimation animationPageTwo; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageTwo = secondaryAnimation as ProxyAnimation; |
| animationPageTwo = animation as ProxyAnimation; |
| return const Text('Page Two'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageTwo = secondaryAnimationProxyPageTwo.parent as ProxyAnimation; |
| expect(animationPageTwo.value, 1.0); |
| expect(secondaryAnimationPageTwo.parent, kAlwaysDismissedAnimation); |
| expect(secondaryAnimationPageOne.parent, animationPageTwo.parent); |
| |
| // Pop page two, the secondary animation of page one becomes |
| // kAlwaysDismissedAnimation. |
| navigator.currentState.pop(); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(secondaryAnimationPageOne.parent, animationPageTwo.parent); |
| await tester.pumpAndSettle(); |
| expect(animationPageTwo.value, 0.0); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| }); |
| |
| testWidgets('secondary animation is kDismissed when next route is removed', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Text('home'), |
| ) |
| ); |
| |
| // Push page one, its secondary animation is kAlwaysDismissedAnimation. |
| ProxyAnimation secondaryAnimationProxyPageOne; |
| ProxyAnimation animationPageOne; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageOne = secondaryAnimation as ProxyAnimation; |
| animationPageOne = animation as ProxyAnimation; |
| return const Text('Page One'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageOne = secondaryAnimationProxyPageOne.parent as ProxyAnimation; |
| expect(animationPageOne.value, 1.0); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| |
| // Push page two, the secondary animation of page one is the primary |
| // animation of page two. |
| ProxyAnimation secondaryAnimationProxyPageTwo; |
| ProxyAnimation animationPageTwo; |
| Route<void> secondRoute; |
| navigator.currentState.push( |
| secondRoute = PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageTwo = secondaryAnimation as ProxyAnimation; |
| animationPageTwo = animation as ProxyAnimation; |
| return const Text('Page Two'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageTwo = secondaryAnimationProxyPageTwo.parent as ProxyAnimation; |
| expect(animationPageTwo.value, 1.0); |
| expect(secondaryAnimationPageTwo.parent, kAlwaysDismissedAnimation); |
| expect(secondaryAnimationPageOne.parent, animationPageTwo.parent); |
| |
| // Remove the second route, the secondary animation of page one is |
| // kAlwaysDismissedAnimation again. |
| navigator.currentState.removeRoute(secondRoute); |
| await tester.pump(); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| }); |
| |
| testWidgets('secondary animation is kDismissed after train hopping finishes and pop', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Text('home'), |
| ) |
| ); |
| |
| // Push page one, its secondary animation is kAlwaysDismissedAnimation. |
| ProxyAnimation secondaryAnimationProxyPageOne; |
| ProxyAnimation animationPageOne; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageOne = secondaryAnimation as ProxyAnimation; |
| animationPageOne = animation as ProxyAnimation; |
| return const Text('Page One'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageOne = secondaryAnimationProxyPageOne.parent as ProxyAnimation; |
| expect(animationPageOne.value, 1.0); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| |
| // Push page two, the secondary animation of page one is the primary |
| // animation of page two. |
| ProxyAnimation animationPageTwo; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| animationPageTwo = animation as ProxyAnimation; |
| return const Text('Page Two'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(secondaryAnimationPageOne.parent, animationPageTwo.parent); |
| |
| // Replace with a different route while push is ongoing to trigger |
| // TrainHopping. |
| ProxyAnimation animationPageThree; |
| navigator.currentState.pushReplacement( |
| TestPageRouteBuilder( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| animationPageThree = animation as ProxyAnimation; |
| return const Text('Page Three'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(secondaryAnimationPageOne.parent, isA<TrainHoppingAnimation>()); |
| final TrainHoppingAnimation trainHopper = secondaryAnimationPageOne.parent as TrainHoppingAnimation; |
| expect(trainHopper.currentTrain, animationPageTwo.parent); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(secondaryAnimationPageOne.parent, isNot(isA<TrainHoppingAnimation>())); |
| expect(secondaryAnimationPageOne.parent, animationPageThree.parent); |
| expect(trainHopper.currentTrain, isNull); // Has been disposed. |
| await tester.pumpAndSettle(); |
| expect(secondaryAnimationPageOne.parent, animationPageThree.parent); |
| |
| // Pop page three. |
| navigator.currentState.pop(); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| }); |
| |
| testWidgets('secondary animation is kDismissed when train hopping is interrupted', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Text('home'), |
| ) |
| ); |
| |
| // Push page one, its secondary animation is kAlwaysDismissedAnimation. |
| ProxyAnimation secondaryAnimationProxyPageOne; |
| ProxyAnimation animationPageOne; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| secondaryAnimationProxyPageOne = secondaryAnimation as ProxyAnimation; |
| animationPageOne = animation as ProxyAnimation; |
| return const Text('Page One'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| final ProxyAnimation secondaryAnimationPageOne = secondaryAnimationProxyPageOne.parent as ProxyAnimation; |
| expect(animationPageOne.value, 1.0); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| |
| // Push page two, the secondary animation of page one is the primary |
| // animation of page two. |
| ProxyAnimation animationPageTwo; |
| navigator.currentState.push( |
| PageRouteBuilder<void>( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| animationPageTwo = animation as ProxyAnimation; |
| return const Text('Page Two'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(secondaryAnimationPageOne.parent, animationPageTwo.parent); |
| |
| // Replace with a different route while push is ongoing to trigger |
| // TrainHopping. |
| navigator.currentState.pushReplacement( |
| TestPageRouteBuilder( |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| return const Text('Page Three'); |
| }, |
| ), |
| ); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 10)); |
| expect(secondaryAnimationPageOne.parent, isA<TrainHoppingAnimation>()); |
| final TrainHoppingAnimation trainHopper = secondaryAnimationPageOne.parent as TrainHoppingAnimation; |
| expect(trainHopper.currentTrain, animationPageTwo.parent); |
| |
| // Pop page three while replacement push is ongoing. |
| navigator.currentState.pop(); |
| await tester.pump(); |
| expect(secondaryAnimationPageOne.parent, isA<TrainHoppingAnimation>()); |
| final TrainHoppingAnimation trainHopper2 = secondaryAnimationPageOne.parent as TrainHoppingAnimation; |
| expect(trainHopper2.currentTrain, animationPageTwo.parent); |
| expect(trainHopper.currentTrain, isNull); // Has been disposed. |
| await tester.pumpAndSettle(); |
| expect(secondaryAnimationPageOne.parent, kAlwaysDismissedAnimation); |
| expect(trainHopper2.currentTrain, isNull); // Has been disposed. |
| }); |
| |
| testWidgets('secondary animation is triggered when pop initial route', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| Animation<double> secondaryAnimationOfRouteOne; |
| Animation<double> primaryAnimationOfRouteTwo; |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| onGenerateRoute: (RouteSettings settings) { |
| return PageRouteBuilder<void>( |
| settings: settings, |
| pageBuilder: (_, Animation<double> animation, Animation<double> secondaryAnimation) { |
| if (settings.name == '/') |
| secondaryAnimationOfRouteOne = secondaryAnimation; |
| else |
| primaryAnimationOfRouteTwo = animation; |
| return const Text('Page'); |
| }, |
| ); |
| }, |
| initialRoute: '/a', |
| ) |
| ); |
| // The secondary animation of the bottom route should be chained with the |
| // primary animation of top most route. |
| expect(secondaryAnimationOfRouteOne.value, 1.0); |
| expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value); |
| // Pops the top most route and verifies two routes are still chained. |
| navigator.currentState.pop(); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 30)); |
| expect(secondaryAnimationOfRouteOne.value, 0.9); |
| expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value); |
| await tester.pumpAndSettle(); |
| expect(secondaryAnimationOfRouteOne.value, 0.0); |
| expect(secondaryAnimationOfRouteOne.value, primaryAnimationOfRouteTwo.value); |
| }); |
| |
| testWidgets('showGeneralDialog uses root navigator by default', (WidgetTester tester) async { |
| final DialogObserver rootObserver = DialogObserver(); |
| final DialogObserver nestedObserver = DialogObserver(); |
| |
| await tester.pumpWidget(MaterialApp( |
| navigatorObservers: <NavigatorObserver>[rootObserver], |
| home: Navigator( |
| observers: <NavigatorObserver>[nestedObserver], |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<dynamic>( |
| builder: (BuildContext context) { |
| return RaisedButton( |
| onPressed: () { |
| showGeneralDialog<void>( |
| context: context, |
| barrierDismissible: false, |
| transitionDuration: Duration.zero, |
| pageBuilder: (BuildContext innerContext, _, __) { |
| return const SizedBox(); |
| }, |
| ); |
| }, |
| child: const Text('Show Dialog'), |
| ); |
| }, |
| ); |
| }, |
| ), |
| )); |
| |
| // Open the dialog. |
| await tester.tap(find.byType(RaisedButton)); |
| |
| expect(rootObserver.dialogCount, 1); |
| expect(nestedObserver.dialogCount, 0); |
| }); |
| |
| testWidgets('showGeneralDialog uses nested navigator if useRootNavigator is false', (WidgetTester tester) async { |
| final DialogObserver rootObserver = DialogObserver(); |
| final DialogObserver nestedObserver = DialogObserver(); |
| |
| await tester.pumpWidget(MaterialApp( |
| navigatorObservers: <NavigatorObserver>[rootObserver], |
| home: Navigator( |
| observers: <NavigatorObserver>[nestedObserver], |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<dynamic>( |
| builder: (BuildContext context) { |
| return RaisedButton( |
| onPressed: () { |
| showGeneralDialog<void>( |
| useRootNavigator: false, |
| context: context, |
| barrierDismissible: false, |
| transitionDuration: Duration.zero, |
| pageBuilder: (BuildContext innerContext, _, __) { |
| return const SizedBox(); |
| }, |
| ); |
| }, |
| child: const Text('Show Dialog'), |
| ); |
| }, |
| ); |
| }, |
| ), |
| )); |
| |
| // Open the dialog. |
| await tester.tap(find.byType(RaisedButton)); |
| |
| expect(rootObserver.dialogCount, 0); |
| expect(nestedObserver.dialogCount, 1); |
| }); |
| |
| testWidgets('reverseTransitionDuration defaults to transitionDuration', (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| |
| // Default MaterialPageRoute transition duration should be 300ms. |
| await tester.pumpWidget(MaterialApp( |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<dynamic>( |
| builder: (BuildContext context) { |
| return RaisedButton( |
| onPressed: () { |
| Navigator.of(context).push<void>( |
| MaterialPageRoute<void>( |
| builder: (BuildContext innerContext) { |
| return Container( |
| key: containerKey, |
| color: Colors.green, |
| ); |
| }, |
| ), |
| ); |
| }, |
| child: const Text('Open page'), |
| ); |
| }, |
| ); |
| }, |
| )); |
| |
| // Open the new route. |
| await tester.tap(find.byType(RaisedButton)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Open page'), findsNothing); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Pop the new route. |
| tester.state<NavigatorState>(find.byType(Navigator)).pop(); |
| await tester.pump(); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container should be present halfway through the transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container should be present at the very end of the transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container have transitioned out after 300ms. |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.byKey(containerKey), findsNothing); |
| }); |
| |
| testWidgets('reverseTransitionDuration can be customized', (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| await tester.pumpWidget(MaterialApp( |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<dynamic>( |
| builder: (BuildContext context) { |
| return RaisedButton( |
| onPressed: () { |
| Navigator.of(context).push<void>( |
| ModifiedReverseTransitionDurationRoute<void>( |
| builder: (BuildContext innerContext) { |
| return Container( |
| key: containerKey, |
| color: Colors.green, |
| ); |
| }, |
| // modified value, default MaterialPageRoute transition duration should be 300ms. |
| reverseTransitionDuration: const Duration(milliseconds: 150), |
| ), |
| ); |
| }, |
| child: const Text('Open page'), |
| ); |
| }, |
| ); |
| }, |
| )); |
| |
| // Open the new route. |
| await tester.tap(find.byType(RaisedButton)); |
| await tester.pumpAndSettle(); |
| expect(find.text('Open page'), findsNothing); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Pop the new route. |
| tester.state<NavigatorState>(find.byType(Navigator)).pop(); |
| await tester.pump(); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container should be present halfway through the transition. |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container should be present at the very end of the transition. |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container have transitioned out after 150ms. |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.byKey(containerKey), findsNothing); |
| }); |
| |
| testWidgets('custom reverseTransitionDuration does not result in interrupted animations', (WidgetTester tester) async { |
| final GlobalKey containerKey = GlobalKey(); |
| await tester.pumpWidget(MaterialApp( |
| theme: ThemeData( |
| pageTransitionsTheme: const PageTransitionsTheme( |
| builders: <TargetPlatform, PageTransitionsBuilder>{ |
| TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(), // use a fade transition |
| }, |
| ), |
| ), |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<dynamic>( |
| builder: (BuildContext context) { |
| return RaisedButton( |
| onPressed: () { |
| Navigator.of(context).push<void>( |
| ModifiedReverseTransitionDurationRoute<void>( |
| builder: (BuildContext innerContext) { |
| return Container( |
| key: containerKey, |
| color: Colors.green, |
| ); |
| }, |
| // modified value, default MaterialPageRoute transition duration should be 300ms. |
| reverseTransitionDuration: const Duration(milliseconds: 150), |
| ), |
| ); |
| }, |
| child: const Text('Open page'), |
| ); |
| }, |
| ); |
| }, |
| )); |
| |
| // Open the new route. |
| await tester.tap(find.byType(RaisedButton)); |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 200)); // jump partway through the forward transition |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Gets the opacity of the fade transition while animating forwards. |
| final double topFadeTransitionOpacity = _getOpacity(containerKey, tester); |
| |
| // Pop the new route mid-transition. |
| tester.state<NavigatorState>(find.byType(Navigator)).pop(); |
| await tester.pump(); |
| |
| // Transition should not jump. In other words, the fade transition |
| // opacity before and after animation changes directions should remain |
| // the same. |
| expect(_getOpacity(containerKey, tester), topFadeTransitionOpacity); |
| |
| // Reverse transition duration should be: |
| // Forward transition elapsed time: 200ms / 300ms = 2 / 3 |
| // Reverse transition remaining time: 150ms * 2 / 3 = 100ms |
| |
| // Container should be present at the very end of the transition. |
| await tester.pump(const Duration(milliseconds: 100)); |
| expect(find.byKey(containerKey), findsOneWidget); |
| |
| // Container have transitioned out after 100ms. |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.byKey(containerKey), findsNothing); |
| }); |
| }); |
| |
| group('ModalRoute', () { |
| testWidgets('default barrierCurve', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: RaisedButton( |
| child: const Text('X'), |
| onPressed: () { |
| Navigator.of(context).push<void>( |
| _TestDialogRouteWithCustomBarrierCurve<void>( |
| child: const Text('Hello World'), |
| ) |
| ); |
| }, |
| ), |
| ); |
| } |
| ), |
| ), |
| )); |
| |
| final CurveTween _defaultBarrierTween = CurveTween(curve: Curves.ease); |
| int _getExpectedBarrierTweenAlphaValue(double t) { |
| return Color.getAlphaFromOpacity(_defaultBarrierTween.transform(t)); |
| } |
| |
| await tester.tap(find.text('X')); |
| await tester.pump(); |
| final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier); |
| expect(animatedModalBarrier, findsOneWidget); |
| |
| Animation<Color> modalBarrierAnimation; |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect(modalBarrierAnimation.value, Colors.transparent); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0), |
| ); |
| |
| await tester.pumpAndSettle(); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect(modalBarrierAnimation.value, Colors.black); |
| }); |
| |
| testWidgets('custom barrierCurve', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return Center( |
| child: RaisedButton( |
| child: const Text('X'), |
| onPressed: () { |
| Navigator.of(context).push<void>( |
| _TestDialogRouteWithCustomBarrierCurve<void>( |
| child: const Text('Hello World'), |
| barrierCurve: Curves.linear, |
| ), |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| final CurveTween _customBarrierTween = CurveTween(curve: Curves.linear); |
| int _getExpectedBarrierTweenAlphaValue(double t) { |
| return Color.getAlphaFromOpacity(_customBarrierTween.transform(t)); |
| } |
| |
| await tester.tap(find.text('X')); |
| await tester.pump(); |
| final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier); |
| expect(animatedModalBarrier, findsOneWidget); |
| |
| Animation<Color> modalBarrierAnimation; |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect(modalBarrierAnimation.value, Colors.transparent); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0), |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 25)); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect( |
| modalBarrierAnimation.value.alpha, |
| closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0), |
| ); |
| |
| await tester.pumpAndSettle(); |
| modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color; |
| expect(modalBarrierAnimation.value, Colors.black); |
| }); |
| |
| testWidgets('focus traverse correct when pop mutiple page simultaneously', (WidgetTester tester) async { |
| // Regression test: https://github.com/flutter/flutter/issues/48903 |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget(MaterialApp( |
| navigatorKey: navigatorKey, |
| home: const Text('dummy1'), |
| )); |
| final Element textOnPageOne = tester.element(find.text('dummy1')); |
| final FocusScopeNode focusNodeOnPageOne = FocusScope.of(textOnPageOne); |
| expect(focusNodeOnPageOne.hasFocus, isTrue); |
| |
| // Pushes one page. |
| navigatorKey.currentState.push<void>( |
| MaterialPageRoute<void>( |
| builder: (BuildContext context) => const Text('dummy2'), |
| ) |
| ); |
| await tester.pumpAndSettle(); |
| |
| final Element textOnPageTwo = tester.element(find.text('dummy2')); |
| final FocusScopeNode focusNodeOnPageTwo = FocusScope.of(textOnPageTwo); |
| // The focus should be on second page. |
| expect(focusNodeOnPageOne.hasFocus, isFalse); |
| expect(focusNodeOnPageTwo.hasFocus, isTrue); |
| |
| // Pushes another page. |
| navigatorKey.currentState.push<void>( |
| MaterialPageRoute<void>( |
| builder: (BuildContext context) => const Text('dummy3'), |
| ) |
| ); |
| await tester.pumpAndSettle(); |
| final Element textOnPageThree = tester.element(find.text('dummy3')); |
| final FocusScopeNode focusNodeOnPageThree = FocusScope.of(textOnPageThree); |
| // The focus should be on third page. |
| expect(focusNodeOnPageOne.hasFocus, isFalse); |
| expect(focusNodeOnPageTwo.hasFocus, isFalse); |
| expect(focusNodeOnPageThree.hasFocus, isTrue); |
| |
| // Pops two pages simultaneously. |
| navigatorKey.currentState.popUntil((Route<void> route) => route.isFirst); |
| await tester.pumpAndSettle(); |
| // It should refocus page one after pops. |
| expect(focusNodeOnPageOne.hasFocus, isTrue); |
| }); |
| |
| testWidgets('focus traversal is correct when popping mutiple pages simultaneously - with focused children', (WidgetTester tester) async { |
| // Regression test: https://github.com/flutter/flutter/issues/48903 |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget(MaterialApp( |
| navigatorKey: navigatorKey, |
| home: const Text('dummy1'), |
| )); |
| final Element textOnPageOne = tester.element(find.text('dummy1')); |
| final FocusScopeNode focusNodeOnPageOne = FocusScope.of(textOnPageOne); |
| expect(focusNodeOnPageOne.hasFocus, isTrue); |
| |
| // Pushes one page. |
| navigatorKey.currentState.push<void>( |
| MaterialPageRoute<void>( |
| builder: (BuildContext context) => const Material(child: TextField()), |
| ) |
| ); |
| await tester.pumpAndSettle(); |
| |
| final Element textOnPageTwo = tester.element(find.byType(TextField)); |
| final FocusScopeNode focusNodeOnPageTwo = FocusScope.of(textOnPageTwo); |
| // The focus should be on second page. |
| expect(focusNodeOnPageOne.hasFocus, isFalse); |
| expect(focusNodeOnPageTwo.hasFocus, isTrue); |
| |
| // Move the focus to another node. |
| focusNodeOnPageTwo.nextFocus(); |
| await tester.pumpAndSettle(); |
| expect(focusNodeOnPageTwo.hasFocus, isTrue); |
| expect(focusNodeOnPageTwo.hasPrimaryFocus, isFalse); |
| |
| // Pushes another page. |
| navigatorKey.currentState.push<void>( |
| MaterialPageRoute<void>( |
| builder: (BuildContext context) => const Text('dummy3'), |
| ) |
| ); |
| await tester.pumpAndSettle(); |
| final Element textOnPageThree = tester.element(find.text('dummy3')); |
| final FocusScopeNode focusNodeOnPageThree = FocusScope.of(textOnPageThree); |
| // The focus should be on third page. |
| expect(focusNodeOnPageOne.hasFocus, isFalse); |
| expect(focusNodeOnPageTwo.hasFocus, isFalse); |
| expect(focusNodeOnPageThree.hasFocus, isTrue); |
| |
| // Pops two pages simultaneously. |
| navigatorKey.currentState.popUntil((Route<void> route) => route.isFirst); |
| await tester.pumpAndSettle(); |
| // It should refocus page one after pops. |
| expect(focusNodeOnPageOne.hasFocus, isTrue); |
| }); |
| |
| testWidgets('child with local history can be disposed', (WidgetTester tester) async { |
| // Regression test: https://github.com/flutter/flutter/issues/52478 |
| await tester.pumpWidget(MaterialApp( |
| home: WidgetWithLocalHistory(), |
| )); |
| |
| final WidgetWithLocalHistoryState state = tester.state(find.byType(WidgetWithLocalHistory)); |
| state.addLocalHistory(); |
| // Waits for modal route to update its internal state; |
| await tester.pump(); |
| |
| // Pumps a new widget to dispose WidgetWithLocalHistory. This should cause |
| // it to remove the local history entry from modal route during |
| // finalizeTree. |
| await tester.pumpWidget(const MaterialApp( |
| home: Text('dummy'), |
| )); |
| // Waits for modal route to update its internal state; |
| await tester.pump(); |
| expect(tester.takeException(), null); |
| }); |
| }); |
| } |
| |
| double _getOpacity(GlobalKey key, WidgetTester tester) { |
| final Finder finder = find.ancestor( |
| of: find.byKey(key), |
| matching: find.byType(FadeTransition), |
| ); |
| return tester.widgetList(finder).fold<double>(1.0, (double a, Widget widget) { |
| final FadeTransition transition = widget as FadeTransition; |
| return a * transition.opacity.value; |
| }); |
| } |
| |
| class ModifiedReverseTransitionDurationRoute<T> extends MaterialPageRoute<T> { |
| ModifiedReverseTransitionDurationRoute({ |
| @required WidgetBuilder builder, |
| RouteSettings settings, |
| this.reverseTransitionDuration, |
| bool fullscreenDialog = false, |
| }) : super( |
| builder: builder, |
| settings: settings, |
| fullscreenDialog: fullscreenDialog, |
| ); |
| |
| @override |
| final Duration reverseTransitionDuration; |
| } |
| |
| class MockPageRoute extends Mock implements PageRoute<dynamic> { } |
| |
| class MockRoute extends Mock implements Route<dynamic> { } |
| |
| class MockRouteAware extends Mock implements RouteAware { } |
| |
| class TestPageRouteBuilder extends PageRouteBuilder<void> { |
| TestPageRouteBuilder({RoutePageBuilder pageBuilder}) : super(pageBuilder: pageBuilder); |
| |
| @override |
| Animation<double> createAnimation() { |
| return CurvedAnimation(parent: super.createAnimation(), curve: Curves.easeOutExpo); |
| } |
| } |
| |
| class DialogObserver extends NavigatorObserver { |
| int dialogCount = 0; |
| |
| @override |
| void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { |
| if (route.toString().contains('_DialogRoute')) { |
| dialogCount++; |
| } |
| super.didPush(route, previousRoute); |
| } |
| } |
| |
| class _TestDialogRouteWithCustomBarrierCurve<T> extends PopupRoute<T> { |
| _TestDialogRouteWithCustomBarrierCurve({ |
| @required Widget child, |
| Curve barrierCurve, |
| }) : _barrierCurve = barrierCurve, |
| _child = child; |
| |
| final Widget _child; |
| |
| @override |
| bool get barrierDismissible => true; |
| |
| @override |
| String get barrierLabel => null; |
| |
| @override |
| Color get barrierColor => Colors.black; // easier value to test against |
| |
| @override |
| Curve get barrierCurve { |
| if (_barrierCurve == null) { |
| return super.barrierCurve; |
| } |
| return _barrierCurve; |
| } |
| final Curve _barrierCurve; |
| |
| @override |
| Duration get transitionDuration => const Duration(milliseconds: 100); // easier value to test against |
| |
| @override |
| Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| return Semantics( |
| child: _child, |
| scopesRoute: true, |
| explicitChildNodes: true, |
| ); |
| } |
| } |
| |
| class WidgetWithLocalHistory extends StatefulWidget { |
| @override |
| WidgetWithLocalHistoryState createState() => WidgetWithLocalHistoryState(); |
| } |
| |
| class WidgetWithLocalHistoryState extends State<WidgetWithLocalHistory> { |
| LocalHistoryEntry _localHistory; |
| |
| void addLocalHistory() { |
| final ModalRoute<dynamic> route = ModalRoute.of(context); |
| _localHistory = LocalHistoryEntry(); |
| route.addLocalHistoryEntry(_localHistory); |
| } |
| |
| @override |
| void dispose() { |
| super.dispose(); |
| _localHistory.remove(); |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| return const Text('dummy'); |
| } |
| } |