| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| class TestTransition extends AnimatedWidget { |
| const TestTransition({ |
| Key? key, |
| required this.childFirstHalf, |
| required this.childSecondHalf, |
| required Animation<double> animation, |
| }) : super(key: key, listenable: animation); |
| |
| final Widget childFirstHalf; |
| final Widget childSecondHalf; |
| |
| @override |
| Widget build(BuildContext context) { |
| final Animation<double> animation = listenable as Animation<double>; |
| if (animation.value >= 0.5) |
| return childSecondHalf; |
| return childFirstHalf; |
| } |
| } |
| |
| class TestRoute<T> extends PageRoute<T> { |
| TestRoute({ |
| required this.child, |
| required RouteSettings settings, |
| this.barrierColor, |
| }) : super(settings: settings); |
| |
| final Widget child; |
| |
| @override |
| Duration get transitionDuration => const Duration(milliseconds: 150); |
| |
| @override |
| final Color? barrierColor; |
| |
| @override |
| String? get barrierLabel => null; |
| |
| @override |
| bool get maintainState => false; |
| |
| @override |
| Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| return child; |
| } |
| } |
| |
| void main() { |
| const Duration kTwoTenthsOfTheTransitionDuration = Duration(milliseconds: 30); |
| const Duration kFourTenthsOfTheTransitionDuration = Duration(milliseconds: 60); |
| |
| testWidgets('Check onstage/offstage handling around transitions', (WidgetTester tester) async { |
| |
| final GlobalKey insideKey = GlobalKey(); |
| |
| String state({ bool skipOffstage = true }) { |
| String result = ''; |
| if (tester.any(find.text('A', skipOffstage: skipOffstage))) |
| result += 'A'; |
| if (tester.any(find.text('B', skipOffstage: skipOffstage))) |
| result += 'B'; |
| if (tester.any(find.text('C', skipOffstage: skipOffstage))) |
| result += 'C'; |
| if (tester.any(find.text('D', skipOffstage: skipOffstage))) |
| result += 'D'; |
| if (tester.any(find.text('E', skipOffstage: skipOffstage))) |
| result += 'E'; |
| if (tester.any(find.text('F', skipOffstage: skipOffstage))) |
| result += 'F'; |
| if (tester.any(find.text('G', skipOffstage: skipOffstage))) |
| result += 'G'; |
| return result; |
| } |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| onGenerateRoute: (RouteSettings settings) { |
| switch (settings.name) { |
| case '/': |
| return TestRoute<void>( |
| settings: settings, |
| child: Builder( |
| key: insideKey, |
| builder: (BuildContext context) { |
| final PageRoute<void> route = ModalRoute.of(context)! as PageRoute<void>; |
| return Column( |
| children: <Widget>[ |
| TestTransition( |
| childFirstHalf: const Text('A'), |
| childSecondHalf: const Text('B'), |
| animation: route.animation!, |
| ), |
| TestTransition( |
| childFirstHalf: const Text('C'), |
| childSecondHalf: const Text('D'), |
| animation: route.secondaryAnimation!, |
| ), |
| ], |
| ); |
| }, |
| ), |
| ); |
| case '/2': return TestRoute<void>(settings: settings, child: const Text('E')); |
| case '/3': return TestRoute<void>(settings: settings, child: const Text('F')); |
| case '/4': return TestRoute<void>(settings: settings, child: const Text('G')); |
| } |
| return null; |
| }, |
| ), |
| ); |
| |
| final NavigatorState navigator = insideKey.currentContext!.findAncestorStateOfType<NavigatorState>()!; |
| |
| expect(state(), equals('BC')); // transition ->1 is at 1.0 |
| |
| navigator.pushNamed('/2'); |
| expect(state(), equals('BC')); // transition 1->2 is not yet built |
| await tester.pump(); |
| expect(state(), equals('BC')); // transition 1->2 is at 0.0 |
| expect(state(skipOffstage: false), equals('BCE')); // E is offstage |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BCE')); // transition 1->2 is at 0.4 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BDE')); // transition 1->2 is at 0.8 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('E')); // transition 1->2 is at 1.0 |
| expect(state(skipOffstage: false), equals('E')); // B and C are gone, the route is inactive with maintainState=false |
| |
| navigator.pop(); |
| expect(state(), equals('E')); // transition 1<-2 is at 1.0, just reversed |
| await tester.pump(); |
| await tester.pump(); |
| |
| expect(state(), equals('BDE')); // transition 1<-2 is at 1.0 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BDE')); // transition 1<-2 is at 0.6 |
| |
| navigator.pushNamed('/3'); |
| expect(state(), equals('BDE')); // transition 1<-2 is at 0.6 |
| await tester.pump(); |
| expect(state(), equals('BDE')); // transition 1<-2 is at 0.6, 1->3 is at 0.0 |
| expect(state(skipOffstage: false), equals('BDEF')); // F is offstage since we're at 0.0 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BCEF')); // transition 1<-2 is at 0.2, 1->3 is at 0.4 |
| expect(state(skipOffstage: false), equals('BCEF')); // nothing secret going on here |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BDF')); // transition 1<-2 is done, 1->3 is at 0.8 |
| |
| navigator.pop(); |
| expect(state(), equals('BDF')); // transition 1<-3 is at 0.8, just reversed |
| await tester.pump(); |
| expect(state(), equals('BDF')); // transition 1<-3 is at 0.8 |
| |
| await tester.pump(kTwoTenthsOfTheTransitionDuration); // notice that dT=0.2 here, not 0.4 |
| expect(state(), equals('BDF')); // transition 1<-3 is at 0.6 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BCF')); // transition 1<-3 is at 0.2 |
| |
| navigator.pushNamed('/4'); |
| expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is not yet built |
| await tester.pump(); |
| expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is at 0.0 |
| expect(state(skipOffstage: false), equals('BCFG')); // G is offstage |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BCG')); // transition 1<-3 is done, 1->4 is at 0.4 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('BDG')); // transition 1->4 is at 0.8 |
| |
| await tester.pump(kFourTenthsOfTheTransitionDuration); |
| expect(state(), equals('G')); // transition 1->4 is done |
| expect(state(skipOffstage: false), equals('G')); // route 1 is not around any more |
| |
| }); |
| |
| testWidgets('Check onstage/offstage handling of barriers around transitions', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| onGenerateRoute: (RouteSettings settings) { |
| switch (settings.name) { |
| case '/': return TestRoute<void>(settings: settings, child: const Text('A')); |
| case '/1': return TestRoute<void>(settings: settings, barrierColor: const Color(0xFFFFFF00), child: const Text('B')); |
| } |
| return null; |
| }, |
| ), |
| ); |
| expect(find.byType(ModalBarrier), findsOneWidget); |
| |
| tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/1'); |
| expect(find.byType(ModalBarrier), findsOneWidget); |
| |
| await tester.pump(); |
| expect(find.byType(ModalBarrier), findsNWidgets(2)); |
| expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).first).color, isNull); |
| expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, isNull); |
| |
| await tester.pump(const Duration(seconds: 1)); |
| expect(find.byType(ModalBarrier), findsOneWidget); |
| expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier)).color, const Color(0xFFFFFF00)); |
| |
| }); |
| } |