| // Copyright 2019 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:animations/src/shared_axis_transition.dart'; |
| import 'package:flutter/animation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/widgets.dart'; |
| import 'package:vector_math/vector_math_64.dart' hide Colors; |
| |
| void main() { |
| group('SharedAxisTransitionType.horizontal', () { |
| testWidgets( |
| 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', |
| (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| await tester.pumpWidget( |
| const SharedAxisPageTransitionsBuilder( |
| transitionType: SharedAxisTransitionType.horizontal, |
| ).buildTransitions<void>( |
| null, |
| null, |
| animation, |
| secondaryAnimation, |
| const Placeholder(), |
| ), |
| ); |
| |
| expect(find.byType(SharedAxisTransition), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs forward', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| // Bottom route is not offset and fully visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| // Top route is offset to the right by 30.0 pixels |
| // and not visible yet. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Bottom route is now invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is still invisible, but moving towards the left. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| double? topOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| expect(topOffset, lessThan(30.0)); |
| expect(topOffset, greaterThan(0.0)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Bottom route is still invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is fading in |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), greaterThan(0)); |
| expect(_getOpacity(topRoute, tester), lessThan(1.0)); |
| topOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| expect(topOffset, greaterThan(0.0)); |
| expect(topOffset, lessThan(30.0)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Bottom route is not visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| -30.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route has no offset and is visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(bottomRoute), findsNothing); |
| expect(find.text(topRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs in reverse', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| |
| navigator.currentState!.pushNamed('/a'); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| expect(find.text(bottomRoute), findsNothing); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Top route is is not offset and fully visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| // Bottom route is offset to the left and is not visible yet. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| -30.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Top route is now invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is still invisible, but moving towards the right. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), |
| moreOrLessEquals(0, epsilon: 0.005)); |
| double? bottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| expect(bottomOffset, lessThan(0.0)); |
| expect(bottomOffset, greaterThan(-30.0)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Top route is still invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is fading in |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| bottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| expect(bottomOffset, lessThan(0.0)); |
| expect(bottomOffset, greaterThan(-30.0)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Top route is not visible and is offset to the right. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is not offset and is visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition does not jump when interrupted', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| |
| // Jump to halfway point of transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| // Bottom route is fully faded out. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| final double? halfwayBottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| expect(halfwayBottomOffset, lessThan(0.0)); |
| expect(halfwayBottomOffset, greaterThan(-30.0)); |
| |
| // Top route is fading/coming in. |
| expect(find.text(topRoute), findsOneWidget); |
| final double? halfwayTopOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ); |
| final double halfwayTopOpacity = _getOpacity(topRoute, tester); |
| expect(halfwayTopOffset, greaterThan(0.0)); |
| expect(halfwayTopOffset, lessThan(30.0)); |
| expect(halfwayTopOpacity, greaterThan(0.0)); |
| expect(halfwayTopOpacity, lessThan(1.0)); |
| |
| // Interrupt the transition with a pop. |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Nothing should change. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| halfwayBottomOffset, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| halfwayTopOffset, |
| ); |
| expect(_getOpacity(topRoute, tester), halfwayTopOpacity); |
| |
| // Jump to the 1/4 (75 ms) point of transition |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| lessThan(0.0), |
| ); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| greaterThan(-30.0), |
| ); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| greaterThan(halfwayBottomOffset), |
| ); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| |
| // Jump to the end. |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.horizontal, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'State is not lost when transitioning', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| contentBuilder: (RouteSettings settings) { |
| return _StatefulTestWidget( |
| key: ValueKey<String?>(settings.name), |
| name: settings.name!, |
| ); |
| }, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| |
| final _StatefulTestWidgetState bottomState = tester.state( |
| find.byKey(const ValueKey<String?>(bottomRoute)), |
| ); |
| expect(bottomState.widget.name, bottomRoute); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| final _StatefulTestWidgetState topState = tester.state( |
| find.byKey(const ValueKey<String?>(topRoute)), |
| ); |
| expect(topState.widget.name, topRoute); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey( |
| const ValueKey<String?>(bottomRoute), |
| skipOffstage: false, |
| )), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect(find.byKey(const ValueKey<String?>(topRoute)), findsNothing); |
| }, |
| ); |
| |
| testWidgets('default fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| // The default fill color should be derived from ThemeData.canvasColor. |
| final Color defaultFillColor = ThemeData().canvasColor; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| }); |
| |
| testWidgets('custom fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| fillColor: Colors.green, |
| transitionType: SharedAxisTransitionType.horizontal, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| }); |
| |
| testWidgets('should keep state', (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: SharedAxisTransition( |
| transitionType: SharedAxisTransitionType.horizontal, |
| child: const _StatefulTestWidget(name: 'Foo'), |
| animation: animation, |
| secondaryAnimation: secondaryAnimation, |
| ), |
| ), |
| )); |
| final State<StatefulWidget> state = tester.state( |
| find.byType(_StatefulTestWidget), |
| ); |
| expect(state, isNotNull); |
| |
| animation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| animation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| }); |
| }); |
| |
| group('SharedAxisTransitionType.vertical', () { |
| testWidgets( |
| 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', |
| (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| await tester.pumpWidget( |
| const SharedAxisPageTransitionsBuilder( |
| transitionType: SharedAxisTransitionType.vertical, |
| ).buildTransitions<void>( |
| null, |
| null, |
| animation, |
| secondaryAnimation, |
| const Placeholder(), |
| ), |
| ); |
| |
| expect(find.byType(SharedAxisTransition), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs forward', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| // Bottom route is not offset and fully visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| // Top route is offset down by 30.0 pixels |
| // and not visible yet. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Bottom route is now invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is still invisible, but moving up. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| double? topOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| expect(topOffset, lessThan(30.0)); |
| expect(topOffset, greaterThan(0.0)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Bottom route is still invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is fading in |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), greaterThan(0)); |
| expect(_getOpacity(topRoute, tester), lessThan(1.0)); |
| topOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| expect(topOffset, greaterThan(0.0)); |
| expect(topOffset, lessThan(30.0)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Bottom route is not visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| -30.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route has no offset and is visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(bottomRoute), findsNothing); |
| expect(find.text(topRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs in reverse', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| |
| navigator.currentState!.pushNamed('/a'); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| expect(find.text(bottomRoute), findsNothing); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Top route is is not offset and fully visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| // Bottom route is offset up and is not visible yet. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| -30.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Top route is now invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is still invisible, but moving down. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getOpacity(bottomRoute, tester), |
| moreOrLessEquals(0, epsilon: 0.005), |
| ); |
| double? bottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| expect(bottomOffset, lessThan(0.0)); |
| expect(bottomOffset, greaterThan(-30.0)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Top route is still invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is fading in |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| bottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| expect(bottomOffset, lessThan(0.0)); |
| expect(bottomOffset, greaterThan(-30.0)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Top route is not visible and is offset down. |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is not offset and is visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition does not jump when interrupted', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| |
| // Jump to halfway point of transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| // Bottom route is fully faded out. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| final double? halfwayBottomOffset = _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| expect(halfwayBottomOffset, lessThan(0.0)); |
| expect(halfwayBottomOffset, greaterThan(-30.0)); |
| |
| // Top route is fading/coming in. |
| expect(find.text(topRoute), findsOneWidget); |
| final double? halfwayTopOffset = _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ); |
| final double halfwayTopOpacity = _getOpacity(topRoute, tester); |
| expect(halfwayTopOffset, greaterThan(0.0)); |
| expect(halfwayTopOffset, lessThan(30.0)); |
| expect(halfwayTopOpacity, greaterThan(0.0)); |
| expect(halfwayTopOpacity, lessThan(1.0)); |
| |
| // Interrupt the transition with a pop. |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Nothing should change. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| halfwayBottomOffset, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| halfwayTopOffset, |
| ); |
| expect(_getOpacity(topRoute, tester), halfwayTopOpacity); |
| |
| // Jump to the 1/4 (75 ms) point of transition |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| lessThan(0.0), |
| ); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| greaterThan(-30.0), |
| ); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| greaterThan(halfwayBottomOffset), |
| ); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| |
| // Jump to the end. |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| bottomRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 0.0, |
| ); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect( |
| _getTranslationOffset( |
| topRoute, |
| tester, |
| SharedAxisTransitionType.vertical, |
| ), |
| 30.0, |
| ); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'State is not lost when transitioning', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| contentBuilder: (RouteSettings settings) { |
| return _StatefulTestWidget( |
| key: ValueKey<String?>(settings.name), |
| name: settings.name!, |
| ); |
| }, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| |
| final _StatefulTestWidgetState bottomState = tester.state( |
| find.byKey(const ValueKey<String?>(bottomRoute)), |
| ); |
| expect(bottomState.widget.name, bottomRoute); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| final _StatefulTestWidgetState topState = tester.state( |
| find.byKey(const ValueKey<String?>(topRoute)), |
| ); |
| expect(topState.widget.name, topRoute); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey( |
| const ValueKey<String?>(bottomRoute), |
| skipOffstage: false, |
| )), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect(find.byKey(const ValueKey<String?>(topRoute)), findsNothing); |
| }, |
| ); |
| |
| testWidgets('default fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| // The default fill color should be derived from ThemeData.canvasColor. |
| final Color defaultFillColor = ThemeData().canvasColor; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| }); |
| |
| testWidgets('custom fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| fillColor: Colors.green, |
| transitionType: SharedAxisTransitionType.vertical, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| }); |
| |
| testWidgets('should keep state', (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: SharedAxisTransition( |
| transitionType: SharedAxisTransitionType.vertical, |
| child: const _StatefulTestWidget(name: 'Foo'), |
| animation: animation, |
| secondaryAnimation: secondaryAnimation, |
| ), |
| ), |
| )); |
| final State<StatefulWidget> state = tester.state( |
| find.byType(_StatefulTestWidget), |
| ); |
| expect(state, isNotNull); |
| |
| animation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| animation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| }); |
| }); |
| |
| group('SharedAxisTransitionType.scaled', () { |
| testWidgets( |
| 'SharedAxisPageTransitionsBuilder builds a SharedAxisTransition', |
| (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| ); |
| |
| await tester.pumpWidget( |
| const SharedAxisPageTransitionsBuilder( |
| transitionType: SharedAxisTransitionType.scaled, |
| ).buildTransitions<void>( |
| null, |
| null, |
| animation, |
| secondaryAnimation, |
| const Placeholder(), |
| ), |
| ); |
| |
| expect(find.byType(SharedAxisTransition), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs forward', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.0); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| // Bottom route is full size and fully visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.0); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| // Top route is at 80% of full size and not visible yet. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 0.8); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Bottom route is now invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is still invisible, but scaling up. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| double topScale = _getScale(topRoute, tester); |
| expect(topScale, greaterThan(0.8)); |
| expect(topScale, lessThan(1.0)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Bottom route is still invisible |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route is fading in |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), greaterThan(0)); |
| expect(_getOpacity(topRoute, tester), lessThan(1.0)); |
| topScale = _getScale(topRoute, tester); |
| expect(topScale, greaterThan(0.8)); |
| expect(topScale, lessThan(1.0)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Bottom route is not visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.1); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| // Top route fully scaled in and visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 1.0); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(bottomRoute), findsNothing); |
| expect(find.text(topRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition runs in reverse', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 1.0); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| expect(find.text(bottomRoute), findsNothing); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Top route is full size and fully visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 1.0); |
| expect(_getOpacity(topRoute, tester), 1.0); |
| // Bottom route is at 110% of full size and not visible yet. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.1); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| |
| // Jump 3/10ths of the way through the transition, bottom route |
| // should be be completely faded out while the top route |
| // is also completely faded out. |
| // Transition time: 300ms, 3/10 * 300ms = 90ms |
| await tester.pump(const Duration(milliseconds: 90)); |
| |
| // Bottom route is now invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Top route is still invisible, but scaling down. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect( |
| _getOpacity(bottomRoute, tester), |
| moreOrLessEquals(0, epsilon: 0.005), |
| ); |
| double bottomScale = _getScale(bottomRoute, tester); |
| expect(bottomScale, greaterThan(1.0)); |
| expect(bottomScale, lessThan(1.1)); |
| |
| // Jump to the middle of fading in |
| await tester.pump(const Duration(milliseconds: 90)); |
| // Top route is still invisible |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route is fading in |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| bottomScale = _getScale(bottomRoute, tester); |
| expect(bottomScale, greaterThan(1.0)); |
| expect(bottomScale, lessThan(1.1)); |
| |
| // Jump to the end of the transition |
| await tester.pump(const Duration(milliseconds: 120)); |
| // Top route is not visible. |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 0.8); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| // Bottom route fully scaled in and visible. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.0); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition does not jump when interrupted', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| |
| // Jump to halfway point of transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| // Bottom route is fully faded out. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| final double halfwayBottomScale = _getScale(bottomRoute, tester); |
| expect(halfwayBottomScale, greaterThan(1.0)); |
| expect(halfwayBottomScale, lessThan(1.1)); |
| |
| // Top route is fading/scaling in. |
| expect(find.text(topRoute), findsOneWidget); |
| final double halfwayTopScale = _getScale(topRoute, tester); |
| final double halfwayTopOpacity = _getOpacity(topRoute, tester); |
| expect(halfwayTopScale, greaterThan(0.8)); |
| expect(halfwayTopScale, lessThan(1.0)); |
| expect(halfwayTopOpacity, greaterThan(0.0)); |
| expect(halfwayTopOpacity, lessThan(1.0)); |
| |
| // Interrupt the transition with a pop. |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| // Nothing should change. |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), halfwayBottomScale); |
| expect(_getOpacity(bottomRoute, tester), 0.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), halfwayTopScale); |
| expect(_getOpacity(topRoute, tester), halfwayTopOpacity); |
| |
| // Jump to the 1/4 (75 ms) point of transition |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), greaterThan(1.0)); |
| expect(_getScale(bottomRoute, tester), lessThan(1.1)); |
| expect(_getScale(bottomRoute, tester), lessThan(halfwayBottomScale)); |
| expect(_getOpacity(bottomRoute, tester), greaterThan(0.0)); |
| expect(_getOpacity(bottomRoute, tester), lessThan(1.0)); |
| |
| // Jump to the end. |
| await tester.pump(const Duration(milliseconds: 75)); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(_getScale(bottomRoute, tester), 1.0); |
| expect(_getOpacity(bottomRoute, tester), 1.0); |
| expect(find.text(topRoute), findsOneWidget); |
| expect(_getScale(topRoute, tester), 0.80); |
| expect(_getOpacity(topRoute, tester), 0.0); |
| |
| await tester.pump(const Duration(milliseconds: 1)); |
| expect(find.text(topRoute), findsNothing); |
| expect(find.text(bottomRoute), findsOneWidget); |
| }, |
| ); |
| |
| testWidgets( |
| 'SharedAxisTransition properly disposes animation', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| expect(find.text(bottomRoute), findsOneWidget); |
| expect(find.text(topRoute), findsNothing); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| |
| // Jump to halfway point of transition. |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(find.byType(SharedAxisTransition), findsNWidgets(2)); |
| |
| // Rebuild the app without the transition. |
| await tester.pumpWidget( |
| MaterialApp( |
| navigatorKey: navigator, |
| home: const Material( |
| child: Text('abc'), |
| ), |
| ), |
| ); |
| await tester.pump(); |
| // Transitions should have been disposed of. |
| expect(find.byType(SharedAxisTransition), findsNothing); |
| }, |
| ); |
| |
| testWidgets( |
| 'State is not lost when transitioning', |
| (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| contentBuilder: (RouteSettings settings) { |
| return _StatefulTestWidget( |
| key: ValueKey<String?>(settings.name), |
| name: settings.name!, |
| ); |
| }, |
| ), |
| ); |
| |
| final _StatefulTestWidgetState bottomState = tester.state( |
| find.byKey(const ValueKey<String?>(bottomRoute)), |
| ); |
| expect(bottomState.widget.name, bottomRoute); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| final _StatefulTestWidgetState topState = tester.state( |
| find.byKey(const ValueKey<String?>(topRoute)), |
| ); |
| expect(topState.widget.name, topRoute); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey( |
| const ValueKey<String?>(bottomRoute), |
| skipOffstage: false, |
| )), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| navigator.currentState!.pop(); |
| await tester.pump(); |
| |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(topRoute))), |
| topState, |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect( |
| tester.state(find.byKey(const ValueKey<String?>(bottomRoute))), |
| bottomState, |
| ); |
| expect(find.byKey(const ValueKey<String?>(topRoute)), findsNothing); |
| }, |
| ); |
| |
| testWidgets('default fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| // The default fill color should be derived from ThemeData.canvasColor. |
| final Color defaultFillColor = ThemeData().canvasColor; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, |
| defaultFillColor); |
| }); |
| |
| testWidgets('custom fill color', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigator = GlobalKey<NavigatorState>(); |
| const String bottomRoute = '/'; |
| const String topRoute = '/a'; |
| |
| await tester.pumpWidget( |
| _TestWidget( |
| navigatorKey: navigator, |
| fillColor: Colors.green, |
| transitionType: SharedAxisTransitionType.scaled, |
| ), |
| ); |
| |
| expect(find.text(bottomRoute), findsOneWidget); |
| Finder fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| |
| navigator.currentState!.pushNamed(topRoute); |
| await tester.pump(); |
| await tester.pumpAndSettle(); |
| |
| fillContainerFinder = find |
| .ancestor( |
| matching: find.byType(Container), |
| of: find.byKey(const ValueKey<String?>('/a')), |
| ) |
| .last; |
| expect(fillContainerFinder, findsOneWidget); |
| expect(tester.widget<Container>(fillContainerFinder).color, Colors.green); |
| }); |
| |
| testWidgets('should keep state', (WidgetTester tester) async { |
| final AnimationController animation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| final AnimationController secondaryAnimation = AnimationController( |
| vsync: const TestVSync(), |
| duration: const Duration(milliseconds: 300), |
| ); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: Center( |
| child: SharedAxisTransition( |
| transitionType: SharedAxisTransitionType.scaled, |
| child: const _StatefulTestWidget(name: 'Foo'), |
| animation: animation, |
| secondaryAnimation: secondaryAnimation, |
| ), |
| ), |
| )); |
| final State<StatefulWidget> state = tester.state( |
| find.byType(_StatefulTestWidget), |
| ); |
| expect(state, isNotNull); |
| |
| animation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.forward(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| secondaryAnimation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| |
| animation.reverse(); |
| await tester.pump(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pump(const Duration(milliseconds: 150)); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| await tester.pumpAndSettle(); |
| expect(state, same(tester.state(find.byType(_StatefulTestWidget)))); |
| }); |
| }); |
| } |
| |
| double _getOpacity(String key, WidgetTester tester) { |
| final Finder finder = find.ancestor( |
| of: find.byKey(ValueKey<String?>(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; |
| }); |
| } |
| |
| double? _getTranslationOffset( |
| String key, |
| WidgetTester tester, |
| SharedAxisTransitionType transitionType, |
| ) { |
| final Finder finder = find.ancestor( |
| of: find.byKey(ValueKey<String?>(key)), |
| matching: find.byType(Transform), |
| ); |
| |
| switch (transitionType) { |
| case SharedAxisTransitionType.horizontal: |
| return tester.widgetList<Transform>(finder).fold<double>(0.0, |
| (double a, Widget widget) { |
| final Transform transition = widget as Transform; |
| final Vector3 translation = transition.transform.getTranslation(); |
| return a + translation.x; |
| }); |
| case SharedAxisTransitionType.vertical: |
| return tester.widgetList<Transform>(finder).fold<double>(0.0, |
| (double a, Widget widget) { |
| final Transform transition = widget as Transform; |
| final Vector3 translation = transition.transform.getTranslation(); |
| return a + translation.y; |
| }); |
| case SharedAxisTransitionType.scaled: |
| // SharedAxisTransitionType.scaled should not return a translation |
| // offset. |
| return null; |
| } |
| } |
| |
| double _getScale(String key, WidgetTester tester) { |
| final Finder finder = find.ancestor( |
| of: find.byKey(ValueKey<String?>(key)), |
| matching: find.byType(ScaleTransition), |
| ); |
| return tester.widgetList(finder).fold<double>(1.0, (double a, Widget widget) { |
| final ScaleTransition transition = widget as ScaleTransition; |
| return a * transition.scale.value; |
| }); |
| } |
| |
| class _TestWidget extends StatelessWidget { |
| const _TestWidget({ |
| required this.navigatorKey, |
| this.contentBuilder, |
| required this.transitionType, |
| this.fillColor, |
| }); |
| |
| final Key navigatorKey; |
| final _ContentBuilder? contentBuilder; |
| final SharedAxisTransitionType transitionType; |
| final Color? fillColor; |
| |
| @override |
| Widget build(BuildContext context) { |
| return MaterialApp( |
| navigatorKey: navigatorKey as GlobalKey<NavigatorState>?, |
| theme: ThemeData( |
| platform: TargetPlatform.android, |
| pageTransitionsTheme: PageTransitionsTheme( |
| builders: <TargetPlatform, PageTransitionsBuilder>{ |
| TargetPlatform.android: SharedAxisPageTransitionsBuilder( |
| fillColor: fillColor, |
| transitionType: transitionType, |
| ), |
| }, |
| ), |
| ), |
| onGenerateRoute: (RouteSettings settings) { |
| return MaterialPageRoute<void>( |
| settings: settings, |
| builder: (BuildContext context) { |
| return contentBuilder != null |
| ? contentBuilder!(settings) |
| : Container( |
| child: Center( |
| key: ValueKey<String?>(settings.name), |
| child: Text(settings.name!), |
| ), |
| ); |
| }, |
| ); |
| }, |
| ); |
| } |
| } |
| |
| class _StatefulTestWidget extends StatefulWidget { |
| const _StatefulTestWidget({Key? key, required this.name}) : super(key: key); |
| |
| final String name; |
| |
| @override |
| State<_StatefulTestWidget> createState() => _StatefulTestWidgetState(); |
| } |
| |
| class _StatefulTestWidgetState extends State<_StatefulTestWidget> { |
| @override |
| Widget build(BuildContext context) { |
| return Text(widget.name); |
| } |
| } |
| |
| typedef _ContentBuilder = Widget Function(RouteSettings settings); |